home *** CD-ROM | disk | FTP | other *** search
/ Aminet 44 / Aminet 44 (2001)(GTI - Schatztruhe)[!][Aug 2001].iso / Aminet / comm / mail / YAM23src.lha / Source / YAM_RE.c < prev    next >
C/C++ Source or Header  |  2001-06-07  |  119KB  |  2,863 lines

  1. /***************************************************************************
  2.  
  3.  YAM - Yet Another Mailer
  4.  Copyright (C) 1995-2000 by Marcel Beck <mbeck@yam.ch>
  5.  Copyright (C) 2000-2001 by YAM Open Source Team
  6.  
  7.  This program is free software; you can redistribute it and/or modify
  8.  it under the terms of the GNU General Public License as published by
  9.  the Free Software Foundation; either version 2 of the License, or
  10.  (at your option) any later version.
  11.  
  12.  This program is distributed in the hope that it will be useful,
  13.  but WITHOUT ANY WARRANTY; without even the implied warranty of
  14.  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15.  GNU General Public License for more details.
  16.  
  17.  You should have received a copy of the GNU General Public License
  18.  along with this program; if not, write to the Free Software
  19.  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  20.  
  21.  YAM Official Support Site :  http://www.yam.ch
  22.  YAM OpenSource project    :  http://sourceforge.net/projects/yamos/
  23.  
  24.  $Id: YAM_RE.c,v 1.25.2.3 2001/06/07 12:12:43 laursen Exp $
  25.  
  26. ***************************************************************************/
  27.  
  28. #include "YAM.h"
  29.  
  30. /* local protos */
  31. LOCAL void RE_PrintFile(char*,struct Part*);
  32. LOCAL void RE_PrintLaTeX(char*,struct Part*);
  33. LOCAL char **Init_ISO8859_to_LaTeX_Tab(char*);
  34. LOCAL char *ISO8859_to_LaTeX(char*);
  35.  
  36.  
  37. /***************************************************************************
  38.  Module: Read
  39. ***************************************************************************/
  40.  
  41. /// RE_GetQuestion
  42. //  Finds previous message in a thread
  43. struct Mail *RE_GetQuestion(long irtid)
  44. {
  45.    struct Folder **flist;
  46.    struct Mail *mail;
  47.    int b;
  48.    
  49.    if (irtid) if (flist = FO_CreateList())
  50.    {
  51.      for (b = 1; b <= (int)*flist; b++) if (MA_GetIndex(flist[b]))
  52.         for (mail = flist[b]->Messages; mail; mail = mail->Next)
  53.            if (mail->cMsgID) if (mail->cMsgID == irtid) { free(flist); return mail; }
  54.      free(flist);
  55.    }
  56.    return NULL;
  57. }
  58. ///
  59. /// RE_GetAnswer
  60. //  Find next message in a thread
  61. struct Mail *RE_GetAnswer(long id)
  62. {
  63.    struct Folder **flist;
  64.    struct Mail *mail;
  65.    int b;
  66.    
  67.    if (id) if (flist = FO_CreateList())
  68.    {
  69.      for (b = 1; b <= (int)*flist; b++) if (MA_GetIndex(flist[b]))
  70.          for (mail = flist[b]->Messages; mail; mail = mail->Next)
  71.             if (mail->cIRTMsgID) if (mail->cIRTMsgID == id) { free(flist); return mail; }
  72.      free(flist);
  73.    }
  74.    return NULL;
  75. }
  76. ///
  77. /// RE_Follow
  78. //  Follows a thread in either direction
  79. SAVEDS ASM void RE_Follow(REG(a1,int *arg))
  80. {  
  81.    int i, direction = arg[0], winnum = arg[1];
  82.    struct Folder **flist;
  83.    struct Mail *fmail = NULL;
  84.    BOOL allloaded = TRUE;
  85.  
  86.    if (flist = FO_CreateList())
  87.    {
  88.       for (i = 1; i < (int)*flist; i++) if (flist[i]->LoadedMode != 2 && flist[i]->Type != FT_SEPARATOR) allloaded = FALSE;
  89.       free(flist);
  90.    }
  91.    if (!allloaded) if (!MUI_Request(G->App, G->RE[winnum]->GUI.WI, 0, GetStr(MSG_MA_ConfirmReq), GetStr(MSG_YesNoReq), GetStr(MSG_RE_FollowThreadReq))) return;
  92.    if (direction == -1) fmail = RE_GetQuestion(G->RE[winnum]->Mail.cIRTMsgID);
  93.    if (direction ==  1) fmail = RE_GetAnswer  (G->RE[winnum]->Mail.cMsgID);
  94.    if (fmail)
  95.    {
  96.       struct MailInfo *mi;
  97.       int pos;
  98.       FO_GetFolderByName(fmail->Folder->Name, &pos);
  99.       set(G->MA->GUI.NL_FOLDERS, MUIA_NList_Active, pos);
  100.       mi = GetMailInfo(fmail);
  101.       set(G->MA->GUI.NL_MAILS, MUIA_NList_Active, mi->Pos);
  102.       RE_ReadMessage(winnum, fmail);
  103.    }
  104.    else DisplayBeep(0);
  105. }
  106. MakeHook(RE_FollowHook, RE_Follow);
  107. ///
  108. /// RE_SwitchMessage
  109. //  Goes to next or previous (new) message in list
  110. void RE_SwitchMessage(int winnum, int direction, BOOL onlynew)
  111. {
  112.    extern struct Hook RE_CloseHook;
  113.    struct Mail *mail = G->RE[winnum]->MailPtr;
  114.    struct MailInfo *mi = GetMailInfo(mail);
  115.    int act = mi->Pos;
  116.    struct Folder *CurrentFolder = mail->Folder;
  117.  
  118.    G->RE[winnum]->LastDirection = direction;
  119.    MA_ChangeFolder(CurrentFolder);
  120.    for (act += direction; act >= 0; act += direction)
  121.    {
  122.       DoMethod(G->MA->GUI.NL_MAILS, MUIM_NList_GetEntry, act, &mail);
  123.       if (!mail) break;
  124.       if (!onlynew || (mail->Status == STATUS_NEW || mail->Status == STATUS_UNR))
  125.       {
  126.          set(G->MA->GUI.NL_MAILS, MUIA_NList_Active, act);
  127.          RE_ReadMessage(winnum, mail);
  128.          return;
  129.       }
  130.    }
  131.  
  132.    // check if there are following/previous folders with unread
  133.    // mails and change to there if the user wants
  134.    if (onlynew)
  135.    {
  136.       if (C->AskJumpUnread)
  137.       {
  138.          struct Folder **flist;
  139.  
  140.          if (flist = FO_CreateList())
  141.          {
  142.             int i;
  143.  
  144.             // look for the current folder in the array
  145.             for (i = 1; i <= (int)*flist; i++)
  146.             {
  147.                if (flist[i] == CurrentFolder)
  148.                   break;
  149.             }
  150.  
  151.             // look for first folder with at least one unread mail
  152.             // and if found read that mail
  153.             for (i += direction; i <= (int)*flist && i >= 1; i += direction)
  154.             {
  155.                if (flist[i]->Type != FT_SEPARATOR && flist[i]->Unread > 0)
  156.                {
  157.                   if (!MUI_Request(G->App, G->RE[winnum]->GUI.WI, 0, GetStr(MSG_MA_ConfirmReq), GetStr(MSG_YesNoReq), GetStr(MSG_RE_MoveNextFolderReq), flist[i]->Name))
  158.                      break;
  159.  
  160.                   MA_ChangeFolder(flist[i]);
  161.                   DoMethod(G->MA->GUI.NL_MAILS, MUIM_NList_GetEntry, MUIV_NList_GetEntry_Active, &mail);
  162.                   if (!mail) break;
  163.                   RE_ReadMessage(winnum, mail);
  164.                   break;
  165.                }
  166.             }
  167.  
  168.             // beep if no folder with unread mails was found
  169.             if (i > (int)*flist || i < 1)
  170.                DisplayBeep(NULL);
  171.  
  172.             free(flist);
  173.          }
  174.       }
  175.       else DisplayBeep(NULL);
  176.    }
  177.    else DoMethod(G->App, MUIM_CallHook, &RE_CloseHook, winnum);
  178. }
  179. ///
  180. /// RE_PrevNext
  181. //  Goes to next or previous (new) message in list
  182. SAVEDS ASM void RE_PrevNext(REG(a1,int *arg))
  183. {  
  184.    BOOL onlynew = arg[1] & (IEQUALIFIER_LSHIFT|IEQUALIFIER_RSHIFT);
  185.    if (arg[3]) return; // Toolbar qualifier bug work-around
  186.    RE_SwitchMessage(arg[2], arg[0], onlynew);
  187. }
  188. MakeHook(RE_PrevNextHook, RE_PrevNext);
  189. ///
  190. /// RE_PrevNextPageFunc
  191. //  Flips one page back or forth
  192. SAVEDS ASM void RE_PrevNextPageFunc(REG(a1,int *arg))
  193. {
  194.    int direct = arg[0], winnum = arg[1], visible;
  195.    struct RE_GUIData *gui = &G->RE[winnum]->GUI;
  196.    get(gui->SL_TEXT, MUIA_Prop_Visible, &visible);
  197.    DoMethod(gui->SL_TEXT, MUIM_Numeric_Increase, visible*direct);
  198. }
  199. MakeHook(RE_PrevNextPageHook, RE_PrevNextPageFunc);
  200. ///
  201. /// RE_UpdateDisplay
  202. //  Updates message display after deleting/moving the current message
  203. void RE_UpdateDisplay(int pos, int winnum)
  204. {
  205.    struct Mail *mail = NULL;
  206.  
  207.    if (G->RE[winnum]->LastDirection == -1) --pos;
  208.    if (pos >= 0)
  209.    {
  210.       DoMethod(G->MA->GUI.NL_MAILS, MUIM_NList_GetEntry, pos, &mail);
  211.       if (mail)
  212.       {
  213.          set(G->MA->GUI.NL_MAILS, MUIA_NList_Active, pos);
  214.          RE_ReadMessage(winnum, mail);
  215.          return;
  216.       }
  217.    }
  218.    DoMethod(G->App, MUIM_CallHook, &RE_CloseHook, winnum);
  219. }
  220. ///
  221. /// RE_UpdateStatusGroup
  222. //  Updates status images (right side of the toolbar)
  223. void RE_UpdateStatusGroup(int winnum)
  224. {
  225.    struct RE_ClassData *re = G->RE[winnum];
  226.    struct RE_GUIData *gui = &re->GUI;
  227.    struct Mail *mail = re->MailPtr;
  228.  
  229.    set(gui->GR_STATUS[0], MUIA_Group_ActivePage, 1+mail->Status);
  230.    set(gui->GR_STATUS[1], MUIA_Group_ActivePage, re->PGPEncrypted ? 1 : 0);
  231.    set(gui->GR_STATUS[2], MUIA_Group_ActivePage, re->PGPSigned ? 1 : 0);
  232. }
  233. ///
  234. /// RE_SendMDN
  235. //  Creates a message disposition notification
  236. void RE_SendMDN(int MDNtype, struct Mail *mail, struct Person *recipient, BOOL sendnow)
  237. {
  238.    static char *MDNMessage[5] =
  239.    {
  240.       "The message written on %s to %s with subject \"%s\" has been displayed. This is no guarantee that the content has been read or understood.\n",
  241.       "The message written on %s to %s with subject \"%s\" has been sent somewhere %s, without being displayed to the user. The user may or may not see the message later.\n",
  242.       "The message written on %s to %s with subject \"%s\" has been processed %s, without being displayed to the user. The user may or may not see the message later.\n",
  243.       "The message written on %s to %s with subject \"%s\" has been deleted %s. The recipient may or may not have seen the message. The recipient may \"undelete\" the message at a later time and read the message.\n",
  244.       "%s doesn't wish to inform you about the disposition of your message written on %s with subject \"%s\".\n"
  245.    };
  246.    struct WritePart *p1 = NewPart(2), *p2, *p3;
  247.    struct TempFile *tf1, *tf2, *tf3;
  248.    char buf[SIZE_LINE], disp[SIZE_DEFAULT], *mode;
  249.    struct Compose comp;
  250.  
  251.    if (tf1 = OpenTempFile("w"))
  252.    {
  253.       char *date = DateStamp2String(&mail->Date, DSS_DATETIME), *rcpt = BuildAddrName2(&mail->To), *subj = mail->Subject;
  254.       p1->Filename = tf1->Filename;
  255.       mode = (MDNtype&MDN_AUTOACT) ? "automatically" : "in response to a user command";
  256.       strcpy(disp, (MDNtype&MDN_AUTOACT) ? "automatic-action/" : "manual-action/");
  257.       strcat(disp, (MDNtype&MDN_AUTOSEND) ? "MDN-sent-automatically; " : "MDN-sent-manually; ");
  258.       switch (MDNtype & MDN_TYPEMASK)
  259.       {
  260.          case MDN_READ: strcat(disp, "displayed");  fprintf(tf1->FP, MDNMessage[0], date, rcpt, subj); break;
  261.          case MDN_DISP: strcat(disp, "dispatched"); fprintf(tf1->FP, MDNMessage[1], date, rcpt, subj, mode); break;
  262.          case MDN_PROC: strcat(disp, "processed");  fprintf(tf1->FP, MDNMessage[2], date, rcpt, subj, mode); break;
  263.          case MDN_DELE: strcat(disp, "deleted");    fprintf(tf1->FP, MDNMessage[3], date, rcpt, subj, mode); break;
  264.          case MDN_DENY: strcat(disp, "denied");     fprintf(tf1->FP, MDNMessage[4], rcpt, date, subj); break;
  265.       }
  266.       fclose(tf1->FP); tf1->FP = NULL;
  267.       SimpleWordWrap(tf1->Filename, 72);
  268.       p2 = p1->Next = NewPart(2);
  269.       if (tf2 = OpenTempFile("w"))
  270.       {
  271.          char mfile[SIZE_MFILE];
  272.          struct Folder *outfolder = FO_GetFolderByType(FT_OUTGOING, NULL);
  273.          struct ExtendedMail *email = MA_ExamineMail(mail->Folder, mail->MailFile, "", TRUE);
  274.          p2->ContentType = "message/disposition-notification";
  275.          p2->Filename = tf2->Filename;
  276.          sprintf(buf, "%s (YAM %s)", C->SMTP_Domain, __YAM_VERSION);
  277.          EmitHeader(tf2->FP, "Reporting-UA", buf);
  278.          if (*email->OriginalRcpt.Address)
  279.          {
  280.             sprintf(buf, "rfc822;%s", BuildAddrName2(&email->OriginalRcpt));
  281.             EmitHeader(tf2->FP, "Original-Recipient", buf);
  282.          }
  283.          sprintf(buf, "rfc822;%s", BuildAddrName(C->EmailAddress, C->RealName));
  284.          EmitHeader(tf2->FP, "Final-Recipient", buf);
  285.          EmitHeader(tf2->FP, "Original-Message-ID", email->MsgID);
  286.          EmitHeader(tf2->FP, "Disposition", disp);
  287.          fclose(tf2->FP);  tf2->FP = NULL;
  288.          p3 = p2->Next = NewPart(2);
  289.          if (tf3 = OpenTempFile("w"))
  290.          {
  291.             char fullfile[SIZE_PATHFILE];
  292.             FILE *fh;
  293.             p3->ContentType = "text/rfc822-headers";
  294.             p3->Filename = tf3->Filename;
  295.             if (StartUnpack(GetMailFile(NULL, mail->Folder, mail), fullfile, mail->Folder))
  296.             {
  297.                if (fh = fopen(fullfile, "r"))
  298.                {
  299.                   while (fgets(buf, SIZE_LINE, fh)) if (*buf == '\n') break; else fputs(buf, tf3->FP);
  300.                   fclose(fh);
  301.                }
  302.                FinishUnpack(fullfile);
  303.             }
  304.             fclose(tf3->FP); tf3->FP = NULL;
  305.             clear(&comp, sizeof(struct Compose));
  306.             comp.MailTo = StrBufCpy(comp.MailTo, BuildAddrName2(recipient));
  307.             comp.Subject = "Disposition Notification";
  308.             comp.ReportType = 1;
  309.             comp.FirstPart = p1;
  310.             if (comp.FH = fopen(MA_NewMailFile(outfolder, mfile, 0), "w"))
  311.             {
  312.                struct Mail *mlist[3];
  313.                mlist[0] = (struct Mail *)1; mlist[2] = NULL;
  314.                WriteOutMessage(&comp);
  315.                fclose(comp.FH);
  316.                if (email = MA_ExamineMail(outfolder, mfile, Status[STATUS_WFS], TRUE))
  317.                {
  318.                   mlist[2] = AddMailToList((struct Mail *)email, outfolder);
  319.                   MA_FreeEMailStruct(email);
  320.                }
  321.                if (sendnow && mlist[2] && !G->TR) MA_SendMList(mlist);
  322.             }
  323.             else ER_NewError(GetStr(MSG_ER_CreateMailError), NULL, NULL);
  324.             FreeStrBuf(comp.MailTo);
  325.             CloseTempFile(tf3);
  326.          }
  327.          MA_FreeEMailStruct(email);
  328.          CloseTempFile(tf2);
  329.       }
  330.       CloseTempFile(tf1);
  331.    }
  332.    FreePartsList(p1);
  333. }
  334. ///
  335. /// RE_DoMDN
  336. //  Handles message disposition requests
  337. BOOL RE_DoMDN(int MDNtype, struct Mail *mail, BOOL multi)
  338. {
  339.    BOOL ignoreall = FALSE;
  340.    int MDNmode;
  341.    switch (MDNtype)
  342.    {
  343.       case MDN_READ: MDNmode = C->MDN_Display; break;
  344.       case MDN_PROC:
  345.       case MDN_DISP: MDNmode = C->MDN_Process; break;
  346.       case MDN_DELE: MDNmode = C->MDN_Delete; break;
  347.       default:       MDNmode = C->MDN_Filter; break;
  348.    }
  349.    if (MDNmode)
  350.    {
  351.       struct ExtendedMail *email = MA_ExamineMail(mail->Folder, mail->MailFile, NULL, TRUE);
  352.       if (*email->ReceiptTo.Address)
  353.       {
  354.          char buttons[SIZE_DEFAULT*2];
  355.          BOOL isonline = TR_IsOnline(), sendnow = C->SendMDNAtOnce && isonline;
  356.          switch (MDNmode)
  357.          {
  358.             case 1: MDNtype = MDN_DENY|MDN_AUTOSEND|MDN_AUTOACT; break;
  359.             case 2: sendnow = FALSE;
  360.                     strcpy(buttons, GetStr(MSG_RE_MDNGads1));
  361.                     if (isonline) strcat(buttons, GetStr(MSG_RE_MDNGads2));
  362.                     strcat(buttons, GetStr(MSG_RE_MDNGads3));
  363.                     if (multi) strcat(buttons, GetStr(MSG_RE_MDNGads4));
  364.                     switch (MUI_Request(G->App, G->MA->GUI.WI, 0, GetStr(MSG_MA_ConfirmReq), buttons, GetStr(MSG_RE_MDNReq)))
  365.                     {
  366.                        case 0: ignoreall = TRUE;
  367.                        case 5: MDNtype = MDN_IGNORE; break;
  368.                        case 3: sendnow = TRUE;
  369.                        case 1: break;
  370.                        case 4: sendnow = TRUE;
  371.                        case 2: MDNtype = MDN_DENY; break;
  372.                     }
  373.                     break;
  374.             case 3: if (MDNtype != MDN_IGNORE) MDNtype |= MDN_AUTOSEND; break;
  375.          }
  376.          if (MDNtype != MDN_IGNORE) RE_SendMDN(MDNtype, mail, &email->ReceiptTo, sendnow);
  377.       }
  378.       MA_FreeEMailStruct(email);
  379.    }
  380.    return ignoreall;
  381. }
  382. ///
  383. /// RE_ReadMessage
  384. //  Displays a message in the read window
  385. void RE_ReadMessage(int winnum, struct Mail *mail)
  386. {
  387.    extern struct Hook RE_CheckSignatureHook;
  388.    struct MailInfo *mi = GetMailInfo(mail);
  389.    struct RE_ClassData *re = G->RE[winnum];
  390.    struct RE_GUIData *gui = &re->GUI;
  391.    int i;
  392.    struct Folder **flist, *folder = mail->Folder;
  393.    BOOL real = !Virtual(mail);
  394.    BOOL out = real ? OUTGOING(folder->Type) : FALSE, allloaded = TRUE;
  395.  
  396.    re->Mail = *mail;
  397.    re->MailPtr = mail;
  398.    re->PGPKey = FALSE;
  399.    re->PGPSigned = re->PGPEncrypted = 0;
  400.    sprintf(re->WTitle, "%s %s %s: ", mail->MailFile, out ? GetStr(MSG_To) : GetStr(MSG_From), out ? AddrName(mail->To) : AddrName(mail->From));
  401.    stccat(re->WTitle, mail->Subject, SIZE_DEFAULT);
  402.    set(gui->WI, MUIA_Window_Title, re->WTitle);
  403.    set(gui->MI_EDIT, MUIA_Menuitem_Enabled, out);
  404.    DoMethod(gui->TE_TEXT, MUIM_TextEditor_ClearText);
  405.    if (gui->TO_TOOLBAR)
  406.    {
  407.       DoMethod(gui->TO_TOOLBAR, MUIM_Toolbar_Set, 0, MUIV_Toolbar_Set_Ghosted, real ? mi->Pos == 0 : TRUE);
  408.       DoMethod(gui->TO_TOOLBAR, MUIM_Toolbar_Set, 1, MUIV_Toolbar_Set_Ghosted, real ? mi->Pos == folder->Total-1 : TRUE);
  409.       DoMethod(gui->TO_TOOLBAR, MUIM_Toolbar_Set, 9, MUIV_Toolbar_Set_Ghosted, !real);
  410.       DoMethod(gui->TO_TOOLBAR, MUIM_Toolbar_Set,10, MUIV_Toolbar_Set_Ghosted, !real);
  411.       DoMethod(gui->TO_TOOLBAR, MUIM_Toolbar_Set,11, MUIV_Toolbar_Set_Ghosted, out);
  412.    }
  413.    if (real)
  414.    {
  415.       if (flist = FO_CreateList())
  416.       {
  417.          for (i = 1; i <= (int)*flist; i++) if (flist[i]->LoadedMode != 2 && flist[i]->Type != FT_SEPARATOR) allloaded = FALSE;
  418.          free(flist);
  419.       }
  420.       if (allloaded && gui->TO_TOOLBAR)
  421.       {
  422.          DoMethod(gui->TO_TOOLBAR, MUIM_Toolbar_Set, 2, MUIV_Toolbar_Set_Ghosted, !RE_GetQuestion(mail->cIRTMsgID));
  423.          DoMethod(gui->TO_TOOLBAR, MUIM_Toolbar_Set, 3, MUIV_Toolbar_Set_Ghosted, !RE_GetAnswer(mail->cMsgID));
  424.       }
  425.    }
  426.    else if (gui->TO_TOOLBAR)
  427.    {
  428.       DoMethod(gui->TO_TOOLBAR, MUIM_Toolbar_Set, 2, MUIV_Toolbar_Set_Ghosted, TRUE);
  429.       DoMethod(gui->TO_TOOLBAR, MUIM_Toolbar_Set, 3, MUIV_Toolbar_Set_Ghosted, TRUE);
  430.    }
  431.    GetMailFile(G->RE[winnum]->File, folder, mail);
  432.    if (RE_LoadMessage(winnum, PM_ALL))
  433.    {
  434.       RE_DisplayMessage(winnum);
  435.       set(gui->MI_EXTKEY, MUIA_Menuitem_Enabled, re->PGPKey);
  436.       set(gui->MI_CHKSIG, MUIA_Menuitem_Enabled, re->PGPSigned > 0);
  437.       set(gui->MI_SAVEDEC, MUIA_Menuitem_Enabled, real && (re->PGPEncrypted&PGPE_MIME) > 0);
  438.       RE_UpdateStatusGroup(winnum);
  439.       MA_StartMacro(MACRO_READ, itoa(winnum));
  440.       if (real && (mail->Status == STATUS_NEW || mail->Status == STATUS_UNR))
  441.       {
  442.          MA_SetMailStatus(mail, STATUS_OLD);
  443.          DisplayStatistics(folder);
  444.          if (re->PGPSigned) DoMethod(G->App, MUIM_CallHook, &RE_CheckSignatureHook, FALSE, winnum);
  445.          RE_DoMDN(MDN_READ, mail, FALSE);
  446.       }
  447.    }
  448.    else
  449.    {
  450.       RE_CleanupMessage(winnum);
  451.       DisposeModulePush(&G->RE[winnum]);
  452.    }
  453. }
  454. ///
  455. /// RE_SaveDisplay
  456. //  Saves current message as displayed
  457. void RE_SaveDisplay(int winnum, FILE *fh)
  458. {
  459.    char *ptr;
  460.  
  461.    if (G->RE[winnum]->Header)
  462.    {
  463.       int i;
  464.       fputs("\033[3m", fh);
  465.       for (i = 0; ; i++)
  466.       {
  467.          DoMethod(G->RE[winnum]->GUI.LV_HEAD, MUIM_NList_GetEntry, i, &ptr);
  468.          if (!ptr) break;
  469.          if (!strcmp(ptr, MUIX_I)) ptr += strlen(MUIX_I);
  470.          fputs(ptr, fh); fputc('\n', fh);
  471.       }
  472.       fputs("\033[23m\n", fh);
  473.    }
  474.    for (ptr = (char *)DoMethod(G->RE[winnum]->GUI.TE_TEXT, MUIM_TextEditor_ExportText); *ptr; ptr++)
  475.       if (*ptr == '\033')
  476.       {
  477.          switch (*++ptr)
  478.          {
  479.             case 'u': fputs("\033[4m", fh); break;
  480.             case 'b': fputs("\033[1m", fh); break;
  481.             case 'i': fputs("\033[3m", fh); break;
  482.             case 'n': fputs("\033[0m", fh); break;
  483.             case 'h': break;
  484.             case '[': if (!strncmp(ptr, "[s:18]", 6))     { fputs("===========================================================", fh); }
  485.                       else if (!strncmp(ptr, "[s:2]", 5)) { fputs("-----------------------------------------------------------", fh); }
  486.             case 'p': while (*ptr != ']' && *ptr && *ptr != '\n') ptr++; break;
  487.          }
  488.       }
  489.       else fputc(*ptr, fh);
  490. }  
  491. ///
  492. /// RE_SuggestName
  493. //  Suggests a file name based on the message subject
  494. char *RE_SuggestName(struct Mail *mail)
  495. {
  496.    static char name[SIZE_FILE];
  497.    char *ptr = mail->Subject;
  498.    int i = 0;
  499.  
  500.    clear(name, SIZE_FILE);
  501.    while (*ptr && i < 26)
  502.    {
  503.       if ((int)*ptr <= ' ') name[i++] = '_';
  504.       else if (*ptr != ':' && *ptr != '/') name[i++] = *ptr;
  505.       ptr++;
  506.    }
  507.    strcat(name, ".msg");
  508.    return name;
  509. }
  510. ///
  511. /// RE_Export
  512. //  Saves message or attachments to disk
  513. BOOL RE_Export(int winnum, char *source, char *dest, char *name, int nr, BOOL force, BOOL overwrite, char *ctype)
  514. {
  515.    char buffer[SIZE_PATHFILE], buffer2[SIZE_FILE+SIZE_DEFAULT];
  516.    APTR win = winnum==4 ? G->MA->GUI.WI : G->RE[winnum]->GUI.WI;
  517.    struct Mail *mail = &G->RE[winnum]->Mail;
  518.  
  519.    if (!*dest)
  520.    {
  521.       if (*name) strcpy(buffer2, name);
  522.       else if (nr) sprintf(buffer2, "%s-%ld", G->RE[winnum]->Mail.MailFile, nr);
  523.       else strcpy(buffer2, RE_SuggestName(&(G->RE[winnum]->Mail)));
  524.       if (force) strmfp(dest = buffer, C->DetachDir, buffer2);
  525.       else if (ReqFile(ASL_DETACH, win, GetStr(MSG_RE_SaveMessage), 1, C->DetachDir, buffer2))
  526.          strmfp(dest = buffer, G->ASLReq[ASL_DETACH]->fr_Drawer, G->ASLReq[ASL_DETACH]->fr_File);
  527.       else return FALSE;
  528.    }
  529.    if (FileExists(dest) && !overwrite)
  530.    {
  531.       sprintf(buffer2, GetStr(MSG_RE_Overwrite), FilePart(dest));
  532.       if (!MUI_Request(G->App, win, 0, GetStr(MSG_MA_ConfirmReq), GetStr(MSG_OkayCancelReq), buffer2)) return FALSE;
  533.    }
  534.    if (!CopyFile(dest, 0, source, 0))
  535.    {
  536.       ER_NewError(GetStr(MSG_ER_CantCreateFile), dest, NULL);
  537.       return FALSE;
  538.    }
  539.    SetComment(dest, BuildAddrName2(&mail->From));
  540.    if (!stricmp(ctype, ContType[CT_AP_AEXE])) SetProtection(dest, 0);
  541.    if (!stricmp(ctype, ContType[CT_AP_SCRIPT])) SetProtection(dest, FIBF_SCRIPT);
  542.    AppendLogVerbose(80, GetStr(MSG_LOG_SavingAtt), dest, mail->MailFile, FolderName(mail->Folder), "");
  543.    return TRUE;
  544. }
  545. ///
  546. /// RE_MoveFunc
  547. //  Moves the current message to another folder
  548. SAVEDS ASM void RE_MoveFunc(REG(a1,int *arg))
  549. {
  550.    int winnum = *arg;
  551.    struct Folder *srcfolder = G->RE[winnum]->Mail.Folder;
  552.    struct Mail *mail = G->RE[winnum]->MailPtr;
  553.    if (MailExists(mail, srcfolder))
  554.    {
  555.       int pos;
  556.       struct Folder *dstfolder = FolderRequest(GetStr(MSG_MA_MoveMsg), GetStr(MSG_MA_MoveMsgReq), GetStr(MSG_MA_MoveGad), GetStr(MSG_Cancel), srcfolder, G->RE[winnum]->GUI.WI);
  557.       if (dstfolder) if ((pos = SelectMessage(mail)) >= 0)
  558.       {
  559.          MA_MoveCopy(mail, srcfolder, dstfolder, FALSE);
  560.          RE_UpdateDisplay(pos, winnum);
  561.          AppendLogNormal(22, GetStr(MSG_LOG_Moving), (void *)1, srcfolder->Name, dstfolder->Name, "");
  562.       }
  563.    }
  564. }
  565. MakeHook(RE_MoveHook, RE_MoveFunc);
  566. ///
  567. /// RE_CopyFunc
  568. //  Copies the current message to another folder
  569. SAVEDS ASM void RE_CopyFunc(REG(a1,int *arg))
  570. {
  571.    int winnum = *arg;
  572.    struct Folder *srcfolder = G->RE[winnum]->Mail.Folder;
  573.    struct Mail *mail = G->RE[winnum]->MailPtr;
  574.    if (MailExists(mail, srcfolder))
  575.    {
  576.       struct Folder *dstfolder = FolderRequest(GetStr(MSG_MA_CopyMsg), GetStr(MSG_MA_MoveMsgReq), GetStr(MSG_MA_CopyGad), GetStr(MSG_Cancel), NULL, G->RE[winnum]->GUI.WI);
  577.       if (dstfolder)
  578.          if (srcfolder)
  579.          {
  580.             MA_MoveCopy(mail, srcfolder, dstfolder, TRUE);
  581.             AppendLogNormal(24, GetStr(MSG_LOG_Copying), (void *)1, srcfolder->Name, dstfolder->Name, "");
  582.          }
  583.          else if (RE_Export(winnum, G->RE[winnum]->File, MA_NewMailFile(dstfolder, mail->MailFile, 0), "", 0, FALSE, FALSE, ContType[CT_ME_EMAIL]))
  584.          {
  585.             APTR lv;
  586.             struct Mail *newmail = AddMailToList(mail, dstfolder);
  587.             if (lv = WhichLV(dstfolder)) DoMethod(lv, MUIM_NList_InsertSingle, newmail, MUIV_NList_Insert_Sorted);
  588.             MA_SetMailStatus(newmail, STATUS_OLD);
  589.          }
  590.    }
  591. }
  592. MakeHook(RE_CopyHook, RE_CopyFunc);
  593. ///
  594. /// RE_DeleteFunc
  595. //  Deletes the current message
  596. SAVEDS ASM void RE_DeleteFunc(REG(a1,int *arg))
  597. {
  598.    BOOL delatonce = arg[0] & (IEQUALIFIER_LSHIFT|IEQUALIFIER_RSHIFT);
  599.    int pos, winnum = arg[1];
  600.    struct Folder *folder = G->RE[winnum]->Mail.Folder, *delfolder = FO_GetFolderByType(FT_DELETED, NULL);
  601.    struct Mail *mail = G->RE[winnum]->MailPtr;
  602.    if (arg[2]) return; // Toolbar qualifier bug work-around
  603.    if (MailExists(mail, folder)) if ((pos = SelectMessage(mail)) >= 0)
  604.    {
  605.       MA_DeleteSingle(G->RE[winnum]->MailPtr, delatonce);
  606.       RE_UpdateDisplay(pos, winnum);
  607.       if (delatonce) AppendLogNormal(20, GetStr(MSG_LOG_Deleting), (void *)1, folder->Name, "", "");
  608.       else           AppendLogNormal(22, GetStr(MSG_LOG_Moving), (void *)1, folder->Name, delfolder->Name, "");
  609.    }
  610. }
  611. MakeHook(RE_DeleteHook, RE_DeleteFunc);
  612. ///
  613. /// RE_PrintFunc
  614. //  Sends the current message or an attachment to the printer
  615. SAVEDS ASM void RE_PrintFunc(REG(a1,int *arg))
  616. {
  617.    int winnum = *arg;
  618.    struct Part *part;
  619.    struct TempFile *prttmp;
  620.  
  621.    if (part = AttachRequest(GetStr(MSG_RE_PrintMsg), GetStr(MSG_RE_SelectPrintPart), GetStr(MSG_RE_PrintGad), GetStr(MSG_Cancel), winnum, ATTREQ_PRINT|ATTREQ_MULTI, G->RE[winnum]->GUI.WI))
  622.        {
  623.       if (C->PrinterCheck) if (!CheckPrinter()) return;
  624.       Busy(GetStr(MSG_BusyDecPrinting), "", 0, 0);
  625.       for (; part; part = part->NextSelected) switch (part->Nr)
  626.       {
  627.          case -2: RE_PrintFile(G->RE[winnum]->File,part);
  628.                   break;
  629.          case -1: if (prttmp = OpenTempFile("w"))
  630.                   {
  631.                      RE_SaveDisplay(winnum, prttmp->FP);
  632.                             fclose(prttmp->FP);
  633.                             prttmp->FP = NULL;
  634.                             RE_PrintFile(prttmp->Filename,part);
  635.                      CloseTempFile(prttmp);
  636.                   }
  637.                   break;
  638.          default: RE_PrintFile(part->Filename,part);
  639.       }
  640.       BusyEnd;
  641.    }
  642. }
  643. MakeHook(RE_PrintHook, RE_PrintFunc);
  644.  
  645. ///
  646. /// RE_PrintFile
  647. //  Prints a file. Currently it is just dumped to PRT:
  648. //  To do for LaTeX printing:
  649. //  - remap characters in header to LaTeX notation
  650. //  - make header lines to print (and parts where to print headers) configurable
  651. LOCAL void RE_PrintFile(char *filename, struct Part *part)
  652. {
  653.     switch(C->PrintMethod)
  654.     {
  655.         case PRINTMETHOD_DUMPRAW :
  656.             CopyFile("PRT:", 0, filename, 0);
  657.             break;
  658.         case PRINTMETHOD_LATEX :
  659.             RE_PrintLaTeX(filename,part);
  660.             break;
  661.         case PRINTMETHOD_POSTSCRIPT :
  662.         default:
  663.             MUI_Request(G->App, NULL, 0, "YAM Error", "OK",
  664.                             "Printing method #%ld is not implemented!\n"
  665.                             "Use 0 for raw printer dump, 1 for LaTeX",C->PrintMethod);
  666.             break;
  667.     }
  668. }
  669.  
  670. LOCAL void RE_PrintLaTeX(char *filename, struct Part *part)
  671. {
  672. struct TempFile *texfile;
  673.  
  674.     if((texfile = OpenTempFile("w")))
  675.     {
  676.         if(CopyFile(NULL,texfile->FP,"YAM:.texheader",NULL))
  677.         {
  678.         char *ts1,*ts2;
  679.  
  680.             if((ts1 = AllocStrBuf(SIZE_LINE)) && (ts2 = AllocStrBuf(SIZE_LINE)))
  681.             {
  682.                 if(1 == part->Nr)
  683.                 {
  684.                 int i,j;
  685.                 char Attrib[SIZE_DEFAULT];
  686.                 char *p,*printcmd;
  687.                 const char PrintScript[] = "YAM:Scripts/LaTeX-print";
  688.  
  689.                     for(i=0; i<Header.Used; i++)
  690.                     {
  691.                         p = Header.Data[i];
  692.                         if(NULL != strchr(p,':'))
  693.                         {
  694.                             for(j=0; p[j] != ':' && j < sizeof(Attrib); j++) Attrib[j] = p[j];
  695.                             Attrib[j++] = ':';
  696.                             Attrib[j++] = '\0';
  697.                             ts1 = StrBufCat(ts1,"\\NewLabWidth{");
  698.                             ts1 = StrBufCat(ts1,Attrib);
  699.                             ts1 = StrBufCat(ts1,"}\n");
  700.                             ts2 = StrBufCat(ts2,Attrib);
  701.                             ts2 = StrBufCat(ts2," &");
  702.                             ts2 = StrBufCat(ts2,p+j-1);
  703.                             ts2 = StrBufCat(ts2,"\\\\\n");
  704.                         } else DB(KPrintF("RE_PrintFile(): strange header line %s\n",p));
  705.                     }
  706.                     fprintf(texfile->FP,"\n%s\n%s\n%s\n%s\n\\input{%s}\n\\end{document}\n",
  707.                                 ts1,
  708.                                 "\\begin{document}\n\n"
  709.                                 "\\setlength{\\tabcolsep}{3.0pt}\n"
  710.                                 "\\setlength{\\TabRestWidth}{\\linewidth}\n"
  711.                                 "\\addtolength{\\TabRestWidth}{-\\tabcolsep}\n"
  712.                                 "\\addtolength{\\TabRestWidth}{-\\LabelWidth}\n\n"
  713.                                 "\\begin{tabular}"
  714.                                 "{@{}>{\\PBS\\raggedleft\\hspace{0pt}\\bf}p{\\LabelWidth}"
  715.                                 ">{\\PBS\\raggedright\\hspace{0pt}}p{\\TabRestWidth}}\n\n",
  716.                                 ts2,
  717.                                 "\\end{tabular}\n"
  718.                                 "\\hrule\n"
  719.                                 "\\bigskip\n",
  720.                                 filename);
  721.                     fclose(texfile->FP);
  722.                     texfile->FP = NULL;
  723.                     if(printcmd = malloc(sizeof(PrintScript)+strlen(texfile->Filename)+1))
  724.                     {
  725.                         strcpy(printcmd,PrintScript);
  726.                         strcat(printcmd," ");
  727.                         strcat(printcmd,texfile->Filename);
  728.                         system(printcmd);
  729.                         free(printcmd);
  730.                     } else DisplayBeep(NULL);
  731.                 } else
  732.                 {
  733.                     DB(KPrintF("RE_PrintFile(): no headers for this part\n"));
  734.                 }
  735.             }
  736.             if(ts1) FreeStrBuf(ts1);
  737.             if(ts2) FreeStrBuf(ts2);
  738.         } else DB(KPrintF("RE_PrintFile(): can't copy YAM:.texheader to temp TeX file\n"));
  739.         CloseTempFile(texfile);
  740.     } else DB(KPrintF("RE_PrintFile(): can't open temp TeX file\n"));
  741. }
  742.  
  743.  
  744. ///
  745. /// ISO8859_to_LaTeX
  746. // Takes a string in ISO-8859 charset and converts it to a equivalent
  747. // string in LaTeX notation. Free the result with FreeStrBuf() after use
  748. LOCAL char *ISO8859_to_LaTeX(char *s)
  749. {
  750. char *result=NULL;
  751. char **CVTab;
  752.  
  753.     if(CVTab = Init_ISO8859_to_LaTeX_Tab("YAM:.latex-chartab"))
  754.     {
  755.     int ResLen;
  756.     char *p;
  757.  
  758.         for(p=s,ResLen=0; *p; p++)  // pre-calculate resulting string's length
  759.             ResLen += (CVTab[*p] == NULL ? 1 : strlen(CVTab[*p]));
  760.  
  761.         DB(KPrintF("ISO8859_to_LaTeX(): source=%ld result=%ld\n",strlen(s),ResLen));
  762.  
  763.         if(result = AllocStrBuf(ResLen+1))
  764.         {
  765.         char *q = result;
  766.             for(p=s,ResLen=0; *p; p++)    // map input string
  767.             {
  768.                 if(CVTab[*p] == NULL)
  769.                     *q++ = *p;
  770.                 else
  771.                 {
  772.                     strcpy(q,CVTab[*p]);
  773.                     while(*q++) ;
  774.                 }
  775.             }
  776.         }
  777.     }
  778.     return result;
  779. }
  780. ///
  781.  
  782. ///
  783. // Init_ISO8859_to_LaTeX_Tab
  784. // Takes a filename for a ISO->LaTeX mapping table and returns a table for
  785. // mapping ISO/ASCII codes to strings
  786. LOCAL char **Init_ISO8859_to_LaTeX_Tab(char *TabFileName)
  787. {
  788. int TabSize;
  789. char **CVTab, *TabFile;
  790. BOOL success=FALSE;
  791.  
  792.     if(-1 != (TabSize = FileSize(TabFileName)))
  793.     {
  794.     BPTR fh;
  795.         if(fh = Open(TabFileName,MODE_OLDFILE))
  796.         {
  797.             if(CVTab = AllocVec(TabSize+1+256*sizeof(char*),MEMF_ANY | MEMF_CLEAR))
  798.             {
  799.                 TabFile = (char*)(CVTab+256*sizeof(char*));
  800.                 if(Read(fh,TabFile,TabSize) == TabSize)
  801.                 {
  802.                 char *tok, c=0;
  803.                     TabFile[TabSize] = '\0';
  804.                     tok = strtok(TabFile," \t\n");
  805.                     while(NULL != tok)
  806.                     {
  807.                         if(!c)
  808.                         {
  809.                             if(tok[1]) DB(KPrintF("Init_ISO8859_to_LaTeX_tab(): line format is %%c %%s\n"));
  810.                             else c = tok[0];
  811.                         } else
  812.                         {
  813.                             CVTab[c] = tok;
  814.                             DB(KPrintF("LaTeX mapping: '%c' -> '%s'\n",c,tok));
  815.                             c = '\0';
  816.                         }
  817.                     }
  818.                     success = TRUE;
  819.                 }
  820.                 if(!success)
  821.                 {
  822.                     FreeMem(CVTab,TabSize);
  823.                     CVTab = NULL;
  824.                 }
  825.             }
  826.             Close(fh);
  827.         }
  828.     }
  829.     return CVTab;
  830. }
  831.  
  832. ///
  833. /// RE_SaveFunc
  834. //  Saves the current message or an attachment to disk
  835. SAVEDS ASM void RE_SaveFunc(REG(a1,int *arg))
  836. {
  837.    int winnum = *arg;
  838.    struct Part *part;
  839.    struct TempFile *tf;
  840.  
  841.    if (part = AttachRequest(GetStr(MSG_RE_SaveMessage), GetStr(MSG_RE_SelectSavePart), GetStr(MSG_RE_SaveGad), GetStr(MSG_Cancel), winnum, ATTREQ_SAVE|ATTREQ_MULTI, G->RE[winnum]->GUI.WI))
  842.    {
  843.       Busy(GetStr(MSG_BusyDecSaving), "", 0, 0);
  844.       for (; part; part = part->NextSelected) switch (part->Nr)
  845.       {
  846.          case -2: RE_Export(winnum, G->RE[winnum]->File, "", "", 0, FALSE, FALSE, ContType[CT_ME_EMAIL]);
  847.                   break;
  848.          case -1: if (tf = OpenTempFile("w"))
  849.                   {
  850.                      RE_SaveDisplay(winnum, tf->FP);
  851.                      fclose(tf->FP); tf->FP = NULL;
  852.                      RE_Export(winnum, tf->Filename, "", "", 0, FALSE, FALSE, ContType[CT_TX_PLAIN]);
  853.                      CloseTempFile(tf);
  854.                   }
  855.                   break;
  856.          default: RE_DecodePart(part);
  857.                   RE_Export(winnum, part->Filename, "", part->Name, part->Nr, FALSE, FALSE, part->ContentType);
  858.       }
  859.       BusyEnd;
  860.    }
  861. }
  862. MakeHook(RE_SaveHook, RE_SaveFunc);
  863. ///
  864. /// RE_DisplayMIME
  865. //  Displays a message part (attachment) using a MIME viewer
  866. void RE_DisplayMIME(char *fname, char *ctype)
  867. {
  868.    static char command[SIZE_COMMAND+SIZE_PATHFILE];
  869.    int i;
  870.    struct MimeView *mv = NULL;
  871.  
  872.    for (i = 1; i < MAXMV; i++) if (C->MV[i])
  873.       if (MatchNoCase(ctype, C->MV[i]->ContentType)) { mv = C->MV[i]; break; }
  874.    if (!mv && !stricmp(ctype, "message/rfc822"))
  875.    {
  876.       int winnum;
  877.       struct Mail *mail;
  878.       struct ExtendedMail *email;
  879.       struct TempFile *tf = OpenTempFile(NULL);
  880.       CopyFile(tf->Filename, NULL, fname, NULL);
  881.       if (email = MA_ExamineMail(NULL, FilePart(tf->Filename), "O", TRUE))
  882.       {
  883.          mail = malloc(sizeof(struct Mail));
  884.          memcpy(mail, &email->Mail, sizeof(struct Mail));
  885.          mail->Folder = NULL;
  886.          mail->Flags |= MFLAG_NOFOLDER;
  887.          MA_FreeEMailStruct(email);
  888.          if ((winnum = RE_Open(-1, FALSE)) != -1)
  889.          {
  890.             G->RE[winnum]->TempFile = tf;
  891.             if (SafeOpenWindow(G->RE[winnum]->GUI.WI)) RE_ReadMessage(winnum, mail);
  892.             else DisposeModulePush(&G->RE[winnum]);
  893.          }
  894.       }
  895.    }
  896.    else
  897.    {
  898.       if (!mv)
  899.       {
  900.          if (C->IdentifyBin)
  901.          {
  902.             ctype = IdentifyFile(fname);
  903.             for (i = 1; i < MAXMV; i++) if (C->MV[i])
  904.                if (MatchNoCase(ctype, C->MV[i]->ContentType)) { mv = C->MV[i]; break; }
  905.          }
  906.          if (!mv) mv = C->MV[0];
  907.       }
  908.       sprintf(command, mv->Command, fname);
  909.       ExecuteCommand(command, TRUE, OUT_NIL);
  910.    }
  911. }
  912. ///
  913. /// RE_DisplayFunc
  914. //  Shows message or attachments separately
  915. SAVEDS ASM void RE_DisplayFunc(REG(a1,int *arg))
  916. {
  917.    int winnum = *arg;
  918.    struct Part *part;
  919.  
  920.    if (part = AttachRequest(GetStr(MSG_RE_DisplayMsg), GetStr(MSG_RE_SelectDisplayPart), GetStr(MSG_RE_DisplayGad), GetStr(MSG_Cancel), winnum, ATTREQ_DISP|ATTREQ_MULTI, G->RE[winnum]->GUI.WI))
  921.    {
  922.       Busy(GetStr(MSG_BusyDecDisplaying), "", 0, 0);
  923.       for (; part; part = part->NextSelected)
  924.       {
  925.          RE_DecodePart(part);
  926.          if (part->Nr == -2) RE_DisplayMIME(G->RE[winnum]->File, "text/plain");
  927.          else RE_DisplayMIME(part->Filename, part->ContentType);
  928.       }
  929.       BusyEnd;
  930.    }
  931. }
  932. MakeHook(RE_DisplayHook, RE_DisplayFunc);
  933. ///
  934. /// RE_SaveAll
  935. //  Saves all attachments to disk
  936. void RE_SaveAll(int winnum, char *path)
  937. {
  938.    struct Part *part;
  939.    char dest[SIZE_PATHFILE], fname[SIZE_FILE];
  940.  
  941.    for (part = G->RE[winnum]->FirstPart->Next->Next; part; part = part->Next)
  942.    {
  943.       if (*part->Name) stccpy(fname, part->Name, SIZE_FILE);
  944.       else sprintf(fname, "%s-%ld", G->RE[winnum]->Mail.MailFile, part->Nr);
  945.       strmfp(dest, path, fname);
  946.       RE_DecodePart(part);
  947.       RE_Export(winnum, part->Filename, dest, part->Name, part->Nr, FALSE, FALSE, part->ContentType);
  948.    }
  949. }
  950. ///
  951. /// RE_SaveAllFunc
  952. //  Asks user for a directory and saves all attachments there
  953. SAVEDS ASM void RE_SaveAllFunc(REG(a1,int *arg))
  954. {
  955.    struct Part *part = G->RE[*arg]->FirstPart->Next;
  956.    if (part) if (part->Next) if (ReqFile(ASL_DETACH, G->RE[*arg]->GUI.WI, GetStr(MSG_RE_SaveMessage), 5, C->DetachDir, ""))
  957.    {
  958.       Busy(GetStr(MSG_BusyDecSaving), "", 0, 0);
  959.       RE_SaveAll(*arg, G->ASLReq[ASL_DETACH]->fr_Drawer);
  960.       BusyEnd;
  961.    }
  962. }
  963. MakeHook(RE_SaveAllHook, RE_SaveAllFunc);
  964. ///
  965. /// RE_RemoveAttachFunc
  966. //  Removes attachments from the current message
  967. SAVEDS ASM void RE_RemoveAttachFunc(REG(a1,int *arg))
  968. {
  969.    struct Mail *mail = G->RE[*arg]->MailPtr;
  970.    struct MailInfo *mi;
  971.    MA_RemoveAttach(mail);
  972.    if ((mi = GetMailInfo(mail))->Pos >= 0)
  973.    {
  974.       DoMethod(G->MA->GUI.NL_MAILS, MUIM_NList_Redraw, mi->Pos);
  975.       MA_ChangeSelectedFunc();
  976.       DisplayStatistics(mail->Folder);
  977.    }
  978.    RE_ReadMessage(*arg, mail);
  979. }
  980. MakeHook(RE_RemoveAttachHook, RE_RemoveAttachFunc);
  981. ///
  982. /// RE_NewFunc
  983. //  Starts a new message based on the current one
  984. SAVEDS ASM void RE_NewFunc(REG(a1,int *arg))
  985. {
  986.    int mode = arg[0], winnum = arg[2], flags = 0;
  987.    ULONG qual = arg[1];
  988.    struct Mail *mail = G->RE[winnum]->MailPtr, *mlist[3] = { (struct Mail *)1, NULL, NULL };
  989.    if (arg[3]) return; // Toolbar qualifier bug work-around
  990.    if (mode == NEW_FORWARD && qual & (IEQUALIFIER_LSHIFT|IEQUALIFIER_RSHIFT)) mode = NEW_BOUNCE;
  991.    if (mode == NEW_FORWARD && qual & IEQUALIFIER_CONTROL) flags = NEWF_FWD_NOATTACH;
  992.    if (mode == NEW_REPLY && qual & (IEQUALIFIER_LSHIFT|IEQUALIFIER_RSHIFT)) flags = NEWF_REP_PRIVATE;
  993.    if (mode == NEW_REPLY && qual & (IEQUALIFIER_LALT|IEQUALIFIER_RALT)) flags = NEWF_REP_MLIST;
  994.    if (mode == NEW_REPLY && qual & IEQUALIFIER_CONTROL) flags = NEWF_REP_NOQUOTE;
  995.    mlist[2] = mail;
  996.    if (MailExists(mail, NULL)) switch (mode)
  997.    {
  998.       case NEW_NEW:     MA_NewNew(mail, flags); break;
  999.       case NEW_EDIT:    MA_NewEdit(mail, flags, winnum); break;
  1000.       case NEW_BOUNCE:  MA_NewBounce(mail, flags); break;
  1001.       case NEW_FORWARD: MA_NewForward(mlist, flags); break;
  1002.       case NEW_REPLY:   MA_NewReply(mlist, flags); break;
  1003.    }
  1004. }
  1005. MakeHook(RE_NewHook, RE_NewFunc);
  1006. ///
  1007. /// RE_GetAddressFunc
  1008. //  Stores sender address of current message in the address book
  1009. SAVEDS ASM void RE_GetAddressFunc(REG(a1,int *arg))
  1010. {
  1011.    int winnum = *arg;
  1012.    struct Folder *folder = G->RE[winnum]->Mail.Folder;
  1013.    struct Mail *mail = G->RE[winnum]->MailPtr, *mlist[3] = { (struct Mail *)1, NULL, NULL };
  1014.    mlist[2] = mail;
  1015.    if (MailExists(mail, folder)) MA_GetAddress(mlist);
  1016. }
  1017. MakeHook(RE_GetAddressHook, RE_GetAddressFunc);
  1018. ///
  1019. /// RE_SetUnreadFunc
  1020. //  Sets the status of the current mail to unread
  1021. SAVEDS ASM void RE_SetUnreadFunc(REG(a1,int *arg))
  1022. {
  1023.    int winnum = *arg;
  1024.    MA_SetMailStatus(G->RE[winnum]->MailPtr, STATUS_UNR);
  1025.    RE_UpdateStatusGroup(winnum);
  1026.    DisplayStatistics(NULL);
  1027. }
  1028. MakeHook(RE_SetUnreadHook, RE_SetUnreadFunc);
  1029. ///
  1030. /// RE_ChangeSubjectFunc
  1031. //  Changes the subject of the current message
  1032. SAVEDS ASM void RE_ChangeSubjectFunc(REG(a1,int *arg))
  1033. {
  1034.    char subj[SIZE_SUBJECT];
  1035.    int winnum = *arg;
  1036.    struct Folder *folder = G->RE[winnum]->Mail.Folder;
  1037.    struct Mail *mail = G->RE[winnum]->MailPtr;
  1038.    struct MailInfo *mi;
  1039.    if (MailExists(mail, folder))
  1040.    {
  1041.       strcpy(subj, mail->Subject);
  1042.       if (StringRequest(subj, SIZE_SUBJECT, GetStr(MSG_MA_ChangeSubj), GetStr(MSG_MA_ChangeSubjReq), GetStr(MSG_Okay), NULL, GetStr(MSG_Cancel), FALSE, G->RE[*arg]->GUI.WI))
  1043.       {
  1044.          MA_ChangeSubject(mail, subj);
  1045.          if ((mi = GetMailInfo(mail))->Pos >= 0)
  1046.          {
  1047.             DoMethod(G->MA->GUI.NL_MAILS, MUIM_NList_Redraw, mi->Pos);
  1048.             MA_ChangeSelectedFunc();
  1049.             DisplayStatistics(mail->Folder);
  1050.          }
  1051.          RE_ReadMessage(*arg, mail);
  1052.       }
  1053.    }
  1054. }
  1055. MakeHook(RE_ChangeSubjectHook, RE_ChangeSubjectFunc);
  1056. ///
  1057. /// RE_ExtractKeyFunc
  1058. //  Extracts public PGP key from the current message
  1059. SAVEDS ASM void RE_ExtractKeyFunc(REG(a1,int *arg))
  1060. {
  1061.    char fullfile[SIZE_PATHFILE], options[SIZE_PATHFILE];
  1062.    struct Mail *mail = G->RE[*arg]->MailPtr;
  1063.  
  1064.    if (!StartUnpack(GetMailFile(NULL, NULL, mail), fullfile, mail->Folder)) return;
  1065.    sprintf(options, (G->PGPVersion == 5) ? "-a %s +batchmode=1 +force" : "-ka %s +bat +f", fullfile);
  1066.    PGPCommand((G->PGPVersion == 5) ? "pgpk" : "pgp", options, 0);
  1067.    FinishUnpack(fullfile);
  1068. }
  1069. MakeHook(RE_ExtractKeyHook, RE_ExtractKeyFunc);
  1070. ///
  1071. /// RE_GetAddressFromLog
  1072. //  Finds e-mail address in PGP output
  1073. BOOL RE_GetAddressFromLog(char *buf, char *address)
  1074. {
  1075.    if (buf = strchr(buf, 34))
  1076.    {
  1077.       stccpy(address, ++buf, SIZE_ADDRESS);
  1078.       if (buf = strchr(address, 34)) *buf = 0;
  1079.       return TRUE;
  1080.    }
  1081.    return FALSE;
  1082. }
  1083. ///
  1084. /// RE_GetSigFromLog
  1085. //  Interprets logfile created from the PGP signature check
  1086. void RE_GetSigFromLog(int winnum, char *decrFor)
  1087. {
  1088.    BOOL sigDone = FALSE, decrFail = FALSE;
  1089.    struct RE_ClassData *re = G->RE[winnum];
  1090.    FILE *fh;
  1091.    char buffer[SIZE_LARGE];
  1092.  
  1093.    if (fh = fopen(PGPLOGFILE, "r"))
  1094.    {
  1095.       while (GetLine(fh, buffer, SIZE_LARGE))
  1096.       {
  1097.          if (!decrFail && decrFor && G->PGPVersion == 5)
  1098.             if (!strnicmp(buffer, "cannot decrypt", 14))
  1099.             {
  1100.                *decrFor = 0;
  1101.                GetLine(fh, buffer, SIZE_LARGE); GetLine(fh, buffer, SIZE_LARGE);
  1102.                RE_GetAddressFromLog(buffer, decrFor);
  1103.                decrFail = TRUE;
  1104.             }
  1105.          if (!sigDone)
  1106.          {
  1107.             if (!strnicmp(buffer, "good signature", 14)) sigDone = TRUE;
  1108.             if (!strnicmp(buffer, "bad signature", 13)) { re->PGPSigned |= PGPS_BADSIG; sigDone = TRUE; }
  1109.             if (sigDone)
  1110.             {
  1111.                if (G->PGPVersion == 5) { GetLine(fh, buffer, SIZE_LARGE); GetLine(fh, buffer, SIZE_LARGE); }
  1112.                if (RE_GetAddressFromLog(buffer, re->Signature)) re->PGPSigned |= PGPS_ADDRESS;
  1113.                re->PGPSigned |= PGPS_CHECKED;
  1114.             }
  1115.          }
  1116.       }
  1117.       fclose(fh);
  1118.       DeleteFile(PGPLOGFILE);
  1119.    }
  1120. }
  1121. ///
  1122. /// RE_CheckSignatureFunc
  1123. //  Checks validity of a PGP signed message
  1124. SAVEDS ASM void RE_CheckSignatureFunc(REG(a1,int *arg))
  1125. {
  1126.    struct RE_ClassData *re = G->RE[arg[1]];
  1127.  
  1128.    if ((re->PGPSigned & PGPS_OLD) && !(re->PGPSigned & PGPS_CHECKED))
  1129.    {
  1130.       int error;
  1131.       char fullfile[SIZE_PATHFILE], options[SIZE_LARGE];
  1132.       if (!StartUnpack(GetMailFile(NULL, NULL, re->MailPtr), fullfile, re->MailPtr->Folder)) return;
  1133.       sprintf(options, (G->PGPVersion == 5) ? "%s -o %s +batchmode=1 +force +language=us" : "%s -o %s +bat +f", fullfile, "T:PGP.tmp");
  1134.       error = PGPCommand((G->PGPVersion == 5) ? "pgpv": "pgp", options, NOERRORS|KEEPLOG);
  1135.       FinishUnpack(fullfile);
  1136.       DeleteFile("T:PGP.tmp");
  1137.       if (error > 0) re->PGPSigned |= PGPS_BADSIG;
  1138.       if (error >= 0) RE_GetSigFromLog(arg[1], NULL); else return;
  1139.    }
  1140.    if ((re->PGPSigned & PGPS_BADSIG) || arg[0])
  1141.    {
  1142.       char buffer[SIZE_LARGE];
  1143.       strcpy(buffer, (re->PGPSigned & PGPS_BADSIG) ? GetStr(MSG_RE_BadSig) : GetStr(MSG_RE_GoodSig));
  1144.       if (re->PGPSigned & PGPS_ADDRESS) { strcat(buffer, GetStr(MSG_RE_SigFrom)); strcat(buffer, re->Signature); }
  1145.       MUI_Request(G->App, re->GUI.WI, 0, GetStr(MSG_RE_SigCheck), GetStr(MSG_Okay), buffer);
  1146.    }
  1147. }
  1148. MakeHook(RE_CheckSignatureHook, RE_CheckSignatureFunc);
  1149. ///
  1150. /// RE_SaveDecryptedFunc
  1151. //  Saves decrypted version of a PGP message
  1152. SAVEDS ASM void RE_SaveDecryptedFunc(REG(a1,int *arg))
  1153. {
  1154.    struct RE_ClassData *re = G->RE[*arg];
  1155.    struct WritePart *p1;
  1156.    struct Compose comp;
  1157.    int choice;
  1158.    struct Folder *folder = re->MailPtr->Folder;
  1159.    char mfile[SIZE_MFILE];
  1160.  
  1161.    if (!(choice = MUI_Request(G->App, re->GUI.WI, 0, GetStr(MSG_RE_SaveDecrypted), GetStr(MSG_RE_SaveDecGads), GetStr(MSG_RE_SaveDecReq)))) return;
  1162.    clear(&comp, sizeof(struct Compose));
  1163.    if (comp.FH = fopen(MA_NewMailFile(folder, mfile, 0), "w"))
  1164.    {
  1165.       struct ExtendedMail *email;
  1166.       struct Mail *new;
  1167.       comp.Mode = NEW_SAVEDEC;
  1168.       comp.OrigMail = re->MailPtr;
  1169.       comp.FirstPart = p1 = NewPart(2);
  1170.       p1->Filename = re->FirstPart->Next->Filename;
  1171.       WriteOutMessage(&comp);
  1172.       FreePartsList(p1);
  1173.       fclose(comp.FH);
  1174.       if (email = MA_ExamineMail(folder, mfile, Status[re->MailPtr->Status], TRUE))
  1175.       {
  1176.          new = AddMailToList((struct Mail *)email, folder);
  1177.          if (FO_GetCurrentFolder() == folder) DoMethod(G->MA->GUI.NL_MAILS, MUIM_NList_InsertSingle, new, MUIV_NList_Insert_Sorted);
  1178.          MA_FreeEMailStruct(email);
  1179.          if (choice == 2)
  1180.          {
  1181.             MA_DeleteSingle(re->MailPtr, FALSE);
  1182.             RE_ReadMessage(*arg, new);
  1183.          }
  1184.       }
  1185.       else ER_NewError(GetStr(MSG_ER_CreateMailError), NULL, NULL);
  1186.    }
  1187. }
  1188. MakeHook(RE_SaveDecryptedHook, RE_SaveDecryptedFunc);
  1189. ///
  1190.  
  1191. /*** MIME ***/
  1192. /// StripTrailingSpace
  1193. //  Strips trailing spaces from a string
  1194. void StripTrailingSpace(char *s)
  1195. {
  1196.    char *t = &s[strlen(s)-1];
  1197.    while (ISpace(*t) && t >= s) *t-- = 0;
  1198. }
  1199. ///
  1200. /// ParamEnd
  1201. //  Finds next parameter in header field
  1202. char *ParamEnd(char *s)
  1203. {
  1204.    BOOL inquotes = FALSE;
  1205.  
  1206.    while (*s) 
  1207.    {
  1208.       if (inquotes) 
  1209.       {
  1210.          if (*s == '"') inquotes = FALSE; else if (*s == '\\') ++s;
  1211.       } 
  1212.       else if (*s == ';') return(s);
  1213.       else if (*s == '"') inquotes = TRUE;
  1214.       ++s;
  1215.    }
  1216.    return NULL;
  1217. }
  1218. ///
  1219. /// Cleanse
  1220. //  Removes trailing and leading spaces and converts string to lower case
  1221. char *Cleanse(char *s)
  1222. {
  1223.    char *tmp, *news;
  1224.    
  1225.    news = s = stpblk(s);
  1226.    for (tmp=s; *tmp; ++tmp) if (isupper((int)*tmp)) *tmp = tolower((int)*tmp);
  1227.    while (tmp > news && *--tmp && ISpace(*tmp)) *tmp = 0;
  1228.    return news;
  1229. }
  1230. ///
  1231. /// UnquoteString
  1232. //  Removes quotes from a string, skipping "escaped" quotes
  1233. char *UnquoteString(char *s, BOOL new)
  1234. {
  1235.    char *ans, *t, *o = s;
  1236.  
  1237.    if (*s != '"') return s;
  1238.    ans = malloc(1+strlen(s));
  1239.    ++s;
  1240.    t = ans;
  1241.    while (*s) 
  1242.    {
  1243.       if (*s == '\\') *t++ = *++s;
  1244.       else if (*s == '"') break;
  1245.       else *t++ = *s;
  1246.       ++s;
  1247.    }
  1248.    *t = 0;
  1249.    if (new) return ans;
  1250.    strcpy(o, ans);
  1251.    free(ans);
  1252.    return o;
  1253. }
  1254. ///
  1255. /// RE_CharIn
  1256. //  Converts character using translation table
  1257. int RE_CharIn(char c, struct TranslationTable *tt)
  1258. {
  1259.    if (tt) if (tt->Header) return (int)tt->Table[(UBYTE)c];
  1260.    return (int)c;
  1261. }
  1262. ///
  1263. /// RE_ProcessHeader (rec)
  1264. //  Processes MIME encoded message headers (RFC-2047)
  1265. STACKEXT void RE_ProcessHeader(char *prevcharset, char *s, BOOL ShowLeadingWhitespace, char *ptr)
  1266. {
  1267.    char *charset, *encoding, *txt, *txtend, *t;
  1268.    int ecode = ENC_NONE, CorrectedCharset = 0;
  1269.    struct TranslationTable *tt = NULL;
  1270.  
  1271.    if (MatchTT(prevcharset, G->TTin, TRUE)) tt = G->TTin;
  1272.    while (*s && (*s != '='))
  1273.    {
  1274.       if (*s == ' ' || *s == '\t') { if (ShowLeadingWhitespace) *ptr++ = ' '; }
  1275.       else 
  1276.       {
  1277.          if (!CorrectedCharset) { CorrectedCharset = TRUE; strcpy(prevcharset, "us-ascii"); }
  1278.          *ptr++ = (char)RE_CharIn(*s, tt);
  1279.       }
  1280.       if (!ShowLeadingWhitespace) ShowLeadingWhitespace = TRUE;
  1281.       ++s;
  1282.    }
  1283.    if (!*s) return;
  1284.    if (*(s+1) != '?') 
  1285.    {
  1286.       *ptr++ = '=';
  1287.       RE_ProcessHeader(prevcharset, ++s, TRUE, ptr);
  1288.       return;
  1289.    }
  1290.    charset = s+2;
  1291.    encoding = strchr(charset, '?');
  1292.    if (!encoding) { *ptr++ = '='; RE_ProcessHeader(prevcharset, ++s, TRUE, ptr); return; }
  1293.    txt = strchr(encoding+1, '?');
  1294.    if (!txt) { *ptr++ = '='; RE_ProcessHeader(prevcharset, ++s, TRUE, ptr); return; }
  1295.    txtend = txt;
  1296.    do { txtend = strchr(txtend+1, '?'); } while(txtend && (*(txtend+1) != '='));
  1297.    if (!txtend) {
  1298.       *ptr++ = '=';
  1299.       RE_ProcessHeader(prevcharset, ++s, TRUE, ptr);
  1300.       return;
  1301.    }
  1302.    *encoding = 0;
  1303.    *txt = 0;
  1304.    *txtend = 0;
  1305.    if (tolower((int)*(encoding+1)) == 'q') ecode = ENC_QP;
  1306.    else if (tolower((int)*(encoding+1)) == 'b') ecode = ENC_B64;
  1307.    else ER_NewError(GetStr(MSG_ER_UnknownHeaderEnc), encoding+1, NULL);
  1308.    if (stricmp(charset, prevcharset))
  1309.    {
  1310.       char *s2;
  1311.       strcpy(prevcharset, charset);
  1312.       for (s2 = prevcharset; *s2; ++s2)
  1313.          if (isupper((int)*s2)) *s2 = tolower((int)*s2);
  1314.       if (MatchTT(prevcharset, G->TTin, TRUE)) tt = G->TTin;
  1315.    }
  1316.    if (ecode == ENC_NONE) for (t = txt+1; *t; ++t) *ptr++ = RE_CharIn(*t, tt);
  1317.    else 
  1318.    {
  1319.       for (t = txt+1; *t; ++t) if (*t == '_') *t = ' ';
  1320.       if (ecode == ENC_B64) from64txt(txt+1, ptr, tt);
  1321.       else if (ecode == ENC_QP) fromqptxt(txt+1, ptr, tt);
  1322.       while (*ptr) ptr++;
  1323.    }
  1324.    *encoding = '?'; *txt = '?'; *txtend = '?';
  1325.    RE_ProcessHeader(prevcharset, txtend+2, TRUE, ptr);
  1326. }
  1327. ///
  1328. /// RE_ParseContentParameters
  1329. //  Parses parameters of Content-Type header field
  1330. void RE_ParseContentParameters(struct Part *rp)
  1331. {
  1332.    char *s, *t, *eq, *ct = rp->ContentType;
  1333.  
  1334.    s = strchr(ct, ';');
  1335.    if (!s) return;
  1336.    *s++ = 0;
  1337.    do {
  1338.       if (t = ParamEnd(s)) *t++ = 0;
  1339.       if (!(eq = strchr(s, '='))) rp->JunkParameter = Cleanse(s);
  1340.       else 
  1341.       {
  1342.          *eq++ = 0;
  1343.          s = Cleanse(s); eq = stpblk(eq);
  1344.          StripTrailingSpace(eq);
  1345.          UnquoteString(eq, FALSE);
  1346.          if (!stricmp(s, "name")) rp->CParName = eq;
  1347.          if (!stricmp(s, "description")) rp->CParDesc = eq;
  1348.          if (!stricmp(s, "boundary")) rp->CParBndr = eq;
  1349.          if (!stricmp(s, "protocol")) rp->CParProt = eq;
  1350.          if (!stricmp(s, "report-type")) rp->CParRType = eq;
  1351.          if (!stricmp(s, "charset")) rp->CParCSet = eq;
  1352.       }
  1353.       s = t;
  1354.    } while (t);
  1355. }
  1356. ///
  1357. /// RE_ScanHeader
  1358. //  Parses the header of the message or of a message part
  1359. BOOL RE_ScanHeader(struct Part *rp, FILE *in, FILE *out, int mode)
  1360. {
  1361.    int i;
  1362.    char *p;
  1363.  
  1364.    if (!MA_ReadHeader(in))
  1365.    {
  1366.       if (mode == 0) ER_NewError(GetStr(MSG_ER_MIMEError), NULL, NULL);
  1367.       else if (mode == 1) ER_NewError(GetStr(MSG_ER_MultipartEOF), NULL, NULL);
  1368.       return FALSE;
  1369.    }
  1370.    rp->HasHeaders = TRUE;
  1371.    for (i = 0; i < Header.Used; i++)
  1372.    {
  1373.       char *s = Header.Data[i];
  1374.       int ls = strlen(s);
  1375.       rp->MaxHeaderLen = MAX(ls, rp->MaxHeaderLen);
  1376.       if (out) { fputs(s, out); fputc('\n', out); }
  1377.       if (!strnicmp(s, "content-type:", 13))
  1378.       {
  1379.          rp->ContentType = StrBufCpy(rp->ContentType, p = stpblk(&s[13]));
  1380.          while (TRUE) 
  1381.          {
  1382.             if (!(p = strchr(rp->ContentType, '/'))) break;
  1383.             if (ISpace(*(p-1)))    for (--p; *p; ++p) *p = *(p+1);
  1384.             else if (ISpace(*++p)) for ( ; *p; ++p) *p = *(p+1);
  1385.             else break;
  1386.          }
  1387.          StripTrailingSpace(rp->ContentType);
  1388.          RE_ParseContentParameters(rp);
  1389.       }
  1390.       else if (!strnicmp(s, "content-transfer-encoding:", 26))
  1391.       {
  1392.          char buf[SIZE_DEFAULT];
  1393.          stccpy(p = buf, stpblk(&s[26]), SIZE_DEFAULT);
  1394.          StripTrailingSpace(p);
  1395.          if      (!stricmp(p, "base64"))           rp->EncodingCode = ENC_B64;
  1396.          else if (!stricmp(p, "quoted-printable")) rp->EncodingCode = ENC_QP;
  1397.          else if (!strnicmp(p, "x-uue", 5))        rp->EncodingCode = ENC_UUE;
  1398.          else if (stricmp(p, "none") && !stricmp(p, "8bit") && !stricmp(p, "7bit"))
  1399.             ER_NewError(GetStr(MSG_ER_UnknownEnc), p, NULL);
  1400.       } 
  1401.       else if (!strnicmp(s, "content-description:", 20))
  1402.       {
  1403.          stccpy(rp->Description, stpblk(&s[20]), SIZE_DEFAULT);
  1404.       } 
  1405.    }
  1406.    for (p = rp->ContentType; *p; ++p) if (isupper((int)*p)) *p = tolower((int)*p);
  1407.    return TRUE;
  1408. }
  1409. ///
  1410. /// RE_ConsumeRestOfPart
  1411. //  Processes body of a message part
  1412. BOOL RE_ConsumeRestOfPart(FILE *in, FILE *out, struct TranslationTable *tt, struct Part *rp)
  1413. {
  1414.    char *ptr, c = 0, buf[SIZE_LINE];
  1415.    UBYTE *p;
  1416.    int blen = 0;
  1417.  
  1418.    if (rp) blen = strlen(rp->Boundary);
  1419.    while (fgets(buf, SIZE_LINE, in))
  1420.    {
  1421.       if (rp) if (!strncmp(buf, rp->Boundary, blen))
  1422.       {
  1423.          if (buf[blen] == '\n') return FALSE;
  1424.          if (buf[blen] == '-' && buf[blen+1] == '-' && buf[blen+2] == '\n') return TRUE;
  1425.       }
  1426.       if (out)
  1427.       {
  1428.          if (c == '\n') fputc(c, out);
  1429.          ptr = &buf[strlen(buf)-1];
  1430.          if ((c = *ptr) == '\n') *ptr = 0;
  1431.          if (tt) for (p = buf; *p; ++p) *p = tt->Table[*p];
  1432.          fputs(buf, out);
  1433.       }
  1434.    }
  1435.    if (out && c == '\n') fputc(c, out);
  1436.    return TRUE;
  1437. }
  1438. ///
  1439. /// RE_DecodeStream
  1440. //  Decodes contents of a part
  1441. void RE_DecodeStream(struct Part *rp, FILE *in, FILE *out)
  1442. {
  1443.    struct TranslationTable *tt = NULL;
  1444.    if (rp->Nr == C->LetterPart && rp->Printable)
  1445.       if (!rp->CParCSet)
  1446.       {
  1447.          if (MatchTT(C->LocalCharset, G->TTin, TRUE) || MatchTT("us-ascii", G->TTin, TRUE)) tt = G->TTin;
  1448.       }
  1449.       else if (MatchTT(rp->CParCSet, G->TTin, TRUE)) tt = G->TTin;
  1450.    switch (rp->EncodingCode)
  1451.    {
  1452.       case ENC_B64:  from64  (in, out, tt, DoesNeedPortableNewlines(rp->ContentType)); break;
  1453.       case ENC_QP:   fromqp  (in, out, tt); break;
  1454.       case ENC_FORM: fromform(in, out, tt); break;
  1455.       case ENC_UUE:  fromuue (in, out); RE_ConsumeRestOfPart(in, NULL, NULL, NULL); break;
  1456.       default:       RE_ConsumeRestOfPart(in, out, tt, NULL);
  1457.    }
  1458. }
  1459. ///
  1460. /// RE_OpenNewPart
  1461. //  Adds a new entry to the message part list
  1462. FILE *RE_OpenNewPart(int winnum, struct Part **new, struct Part *prev, struct Part *first)
  1463. {
  1464.    FILE *fp;
  1465.    if ((*new) = calloc(1,sizeof(struct Part)))
  1466.    {
  1467.       char file[SIZE_FILE];
  1468.       if (prev)
  1469.       {
  1470.          (*new)->Prev = prev;
  1471.          prev->Next = *new;
  1472.          (*new)->Nr = prev->Nr+1;
  1473.       }
  1474.       (*new)->ContentType = StrBufCpy(NULL, "text/plain");
  1475.       (*new)->EncodingCode = ENC_NONE;
  1476.       if (first) if (strnicmp(first->ContentType, "multipart", 9))
  1477.       {
  1478.          (*new)->ContentType = StrBufCpy((*new)->ContentType, first->ContentType);
  1479.          (*new)->CParCSet = first->CParCSet;
  1480.          (*new)->EncodingCode = first->EncodingCode;
  1481.       }
  1482.       strcpy((*new)->Boundary, first ? first->Boundary : (prev ? prev->Boundary : ""));
  1483.       (*new)->Win = winnum;
  1484.       sprintf(file, "YAMraw-w%ldp%ld.txt", winnum, (*new)->Nr);
  1485.       strmfp((*new)->Filename, C->TempDir, file);
  1486.       if (fp = fopen((*new)->Filename, "w")) return fp;
  1487.       free(*new);
  1488.    }
  1489.    return NULL;
  1490. }
  1491. ///
  1492. /// RE_UndoPart
  1493. //  Removes an entry from the message part list
  1494. void RE_UndoPart(struct Part *rp)
  1495. {
  1496.    DeleteFile(rp->Filename);
  1497.    if (rp->Prev) rp->Prev->Next = rp->Next;
  1498.    if (rp->Next) rp->Next->Prev = rp->Prev;
  1499.    if (rp->ContentType) FreeStrBuf(rp->ContentType);
  1500.    free(rp);
  1501. }
  1502. ///
  1503. /// RE_RequiresSpecialHandling
  1504. //  Checks if part is PGP signed/encrypted or a MDN
  1505. int RE_RequiresSpecialHandling(struct Part *hrp)
  1506. {
  1507.    if (!stricmp(hrp->ContentType, "multipart/report") && !stricmp(hrp->CParRType, "disposition-notification")) return 1;
  1508.    if (!stricmp(hrp->ContentType, "multipart/signed") && !stricmp(hrp->CParProt, "application/pgp-signature")) return 2;
  1509.    if (!stricmp(hrp->ContentType, "multipart/encrypted") && !stricmp(hrp->CParProt, "application/pgp-encrypted")) return 3;
  1510.    return 0;
  1511. }
  1512. ///
  1513. /// RE_IsURLencoded
  1514. //  Checks if part contains encoded form data
  1515. BOOL RE_IsURLencoded(struct Part *rp)
  1516. {
  1517.    return (BOOL)(!stricmp(rp->ContentType, "application/x-www-form-urlencoded") ||
  1518.                  !stricmp(rp->ContentType, "application/x-url-encoded"));
  1519. }
  1520. ///
  1521. /// RE_SaveThisPart
  1522. //  Decides if the part should be kept in memory
  1523. BOOL RE_SaveThisPart(struct Part *rp)
  1524. {
  1525.    int pm = G->RE[rp->Win]->ParseMode;
  1526.    switch (pm)
  1527.    {
  1528.       case PM_ALL:   return TRUE;
  1529.       case PM_NONE:  return FALSE;
  1530.       case PM_TEXTS: return (BOOL)(!strnicmp(rp->ContentType, "text", 4) || RE_IsURLencoded(rp));
  1531.    }
  1532.  
  1533.    return FALSE;
  1534. }
  1535. ///
  1536. /// RE_SetPartInfo
  1537. //  Determines size and other information of a message part
  1538. void RE_SetPartInfo(struct Part *rp)
  1539. {
  1540.    int size = rp->Size = FileSize(rp->Filename);
  1541.    if (!rp->Decoded && rp->Nr) switch (rp->EncodingCode)
  1542.    {
  1543.       case ENC_UUE: case ENC_B64: rp->Size = (100*size)/136; break;
  1544.       case ENC_QP:                rp->Size = (100*size)/106; break;
  1545.    }
  1546.    if (!*rp->Name && rp->CParName) { stccpy(rp->Name, rp->CParName, SIZE_FILE); UnquoteString(rp->Name, FALSE); }
  1547.    switch (rp->Nr)
  1548.    {
  1549.       case 0:  SetComment(rp->Filename, GetStr(MSG_RE_Header)); break;
  1550.       case 1:  SetComment(rp->Filename, GetStr(MSG_RE_Letter)); break;
  1551.       default: SetComment(rp->Filename, *rp->Description ? rp->Description : rp->Name); break;
  1552.    }
  1553.    rp->Printable = !strnicmp(rp->ContentType, "text", 4) || rp->Nr == 0;
  1554. }
  1555. ///
  1556. /// RE_ParseMessage
  1557. //  Parses a complete message
  1558. struct Part *RE_ParseMessage(int winnum, FILE *in, char *fname, struct Part *hrp)
  1559. {
  1560.    if (fname) in = fopen(fname, "r");
  1561.    if (in)
  1562.    {
  1563.       FILE *out;
  1564.       struct Part *rp;
  1565.       char *boundary;
  1566.       if (!hrp) if (out = RE_OpenNewPart(winnum, &hrp, NULL, NULL))
  1567.       {
  1568.          BOOL parse_ok = RE_ScanHeader(hrp, in, out, 0);
  1569.          fclose(out);
  1570.          if (parse_ok) RE_SetPartInfo(hrp);
  1571.       }
  1572.       else ER_NewError(GetStr(MSG_ER_CantCreateTempfile), NULL, NULL);
  1573.       if (hrp)
  1574.       {
  1575.          if (!(boundary = hrp->CParBndr)) boundary = hrp->JunkParameter;
  1576.          if (!strnicmp(hrp->ContentType, "multipart", 9))
  1577.          {
  1578.             if (!boundary) ER_NewError(GetStr(MSG_ER_MissingBoundary), NULL, NULL);
  1579.             else
  1580.             {
  1581.                BOOL done;
  1582.                if (*boundary == '"') boundary = UnquoteString(boundary, TRUE);
  1583.                sprintf(hrp->Boundary, "--%s", boundary);
  1584.                done = RE_ConsumeRestOfPart(in, NULL, NULL, hrp);
  1585.                rp = hrp;
  1586.                while (!done)
  1587.                {
  1588.                   struct Part *prev = rp, *newrp;
  1589.                   out = RE_OpenNewPart(winnum, &rp, prev, hrp);
  1590.                   if (!RE_ScanHeader(rp, in, out, 1)) break;
  1591.                   if (!strnicmp(rp->ContentType, "multipart", 9))
  1592.                   {
  1593.                      fclose(out);
  1594.                      if (newrp = RE_ParseMessage(winnum, in, NULL, rp))
  1595.                      {
  1596.                         RE_UndoPart(rp);
  1597.                         done = RE_ConsumeRestOfPart(in, NULL, NULL, prev);
  1598.                         for (rp = prev; rp->Next; rp = rp->Next);
  1599.                      }
  1600.                   }
  1601.                   else if (RE_SaveThisPart(rp) || RE_RequiresSpecialHandling(hrp) == 3)
  1602.                   {
  1603.                      fputc('\n', out);
  1604.                      done = RE_ConsumeRestOfPart(in, out, NULL, rp);
  1605.                      fclose(out);
  1606.                      RE_SetPartInfo(rp);
  1607.                   }
  1608.                   else
  1609.                   {
  1610.                      fclose(out);
  1611.                      done = RE_ConsumeRestOfPart(in, NULL, NULL, rp);
  1612.                      RE_UndoPart(rp);
  1613.                      rp = prev;
  1614.                   }
  1615.                }
  1616.             }
  1617.          }
  1618.          else if (out = RE_OpenNewPart(winnum, &rp, hrp, hrp))
  1619.          {
  1620.             if (RE_SaveThisPart(rp) || RE_RequiresSpecialHandling(hrp) == 3)
  1621.             {
  1622.                RE_ConsumeRestOfPart(in, out, NULL, NULL); fclose(out);
  1623.                RE_SetPartInfo(rp);
  1624.             }
  1625.             else
  1626.             {
  1627.                fclose(out); RE_UndoPart(rp);
  1628.                RE_ConsumeRestOfPart(in, NULL, NULL, NULL);
  1629.             }
  1630.          }
  1631.       }
  1632.       if (fname) fclose(in);
  1633.    } 
  1634.    return hrp;
  1635. }
  1636. ///
  1637. /// RE_DecodePart
  1638. //  Decodes a single message part
  1639. BOOL RE_DecodePart(struct Part *rp)
  1640. {
  1641.    if (!rp->Decoded)
  1642.    {
  1643.       FILE *in, *out;
  1644.       char file[SIZE_FILE], buf[SIZE_LINE], ext[FNSIZE];
  1645.       if (in = fopen(rp->Filename, "r"))
  1646.       {
  1647.          if (rp->HasHeaders) while (GetLine(in, buf, SIZE_LINE)) if (!*buf) break;
  1648.          stcgfe(ext, rp->Name);
  1649.          if (strlen(ext) > 10) *ext = 0;
  1650.          sprintf(file, "YAMmsg-w%ldp%ld.%s", rp->Win, rp->Nr, *ext ? ext : "tmp");
  1651.          strmfp(buf, C->TempDir, file);
  1652.          if (out = fopen(buf, "w"))
  1653.          {
  1654.             RE_DecodeStream(rp, in, out);
  1655.             fclose(out);
  1656.             fclose(in);
  1657.             DeleteFile(rp->Filename);
  1658.             strcpy(rp->Filename, buf);
  1659.             rp->Decoded = TRUE;
  1660.             RE_SetPartInfo(rp);
  1661.          }
  1662.          else fclose(in);
  1663.       }
  1664.    }
  1665.    return rp->Decoded;
  1666. }
  1667. ///
  1668. /// RE_CleanupMessage
  1669. //  Cleanup memory and temporary files used to display the message
  1670. void RE_CleanupMessage(int winnum)
  1671. {
  1672.    struct RE_ClassData *re = G->RE[winnum];
  1673.    struct Part *part, *next;
  1674.  
  1675.    for (part = re->FirstPart; part; part = next)
  1676.    {
  1677.       next = part->Next;
  1678.       if (*part->Filename) DeleteFile(part->Filename);
  1679.       if (part->ContentType) FreeStrBuf(part->ContentType);
  1680.       free(part);
  1681.    }
  1682.    re->FirstPart     = NULL;
  1683.    re->FirstReadDone = FALSE;
  1684.    FinishUnpack(re->File);
  1685.  
  1686. }
  1687. ///
  1688. /// RE_HandleMDNReport
  1689. //  Translates a message disposition notification to readable text
  1690. void RE_HandleMDNReport(struct Part *frp)
  1691. {
  1692.    struct Part *rp[3];
  1693.    char file[SIZE_FILE], buf[SIZE_PATHFILE], MDNtype[SIZE_DEFAULT];
  1694.    char *msgdesc, *mode = "", *type;
  1695.    int i, j;
  1696.    FILE *out, *fh;
  1697.  
  1698.    if (rp[0] = frp->Next) if (rp[1] = rp[0]->Next)
  1699.    {
  1700.       rp[2] = rp[1]->Next;
  1701.       msgdesc = AllocStrBuf(80);
  1702.       strcpy(MDNtype, "");
  1703.       for (j = 1; j < (rp[2] ? 3 : 2); j++)
  1704.       {
  1705.          RE_DecodePart(rp[j]);
  1706.          if (fh = fopen(rp[j]->Filename, "r"))
  1707.          {
  1708.             MA_ReadHeader(fh);
  1709.             fclose(fh);
  1710.             for (i = 0; i < Header.Used; i++)
  1711.             {
  1712.                char *value, *field = Header.Data[i];
  1713.                if (value = strchr(field, ':'))
  1714.                {
  1715.                   *value++ = 0;
  1716.                   if (!stricmp(field, "from")) msgdesc = StrBufCat(StrBufCat(msgdesc, GetStr(MSG_RE_MDNFrom)), value);
  1717.                   else if (!stricmp(field, "to")) msgdesc = StrBufCat(StrBufCat(msgdesc, GetStr(MSG_RE_MDNTo)), value);
  1718.                   else if (!stricmp(field, "subject")) msgdesc = StrBufCat(StrBufCat(msgdesc, GetStr(MSG_RE_MDNSubject)), value);
  1719.                   else if (!stricmp(field, "original-message-id")) msgdesc = StrBufCat(StrBufCat(msgdesc, GetStr(MSG_RE_MDNMessageID)), value);
  1720.                   else if (!stricmp(field, "date")) msgdesc = StrBufCat(StrBufCat(msgdesc, GetStr(MSG_RE_MDNDate)), value);
  1721.                   else if (!stricmp(field, "original-recipient")) msgdesc = StrBufCat(StrBufCat(msgdesc, GetStr(MSG_RE_MDNOrigRecpt)), value);
  1722.                   else if (!stricmp(field, "final-recipient")) msgdesc = StrBufCat(StrBufCat(msgdesc, GetStr(MSG_RE_MDNFinalRecpt)), value);
  1723.                   else if (!stricmp(field, "disposition")) stccpy(MDNtype, Trim(value), SIZE_DEFAULT);
  1724.                }
  1725.             }
  1726.             FreeData2D(&Header);
  1727.          }
  1728.       }
  1729.       msgdesc = StrBufCat(msgdesc, "\n");
  1730.       if (!strnicmp(MDNtype, "manual-action", 13)) mode = GetStr(MSG_RE_MDNmanual);
  1731.       if (!strnicmp(MDNtype, "automatic-action", 16)) mode = GetStr(MSG_RE_MDNauto);
  1732.       if (type = strchr(MDNtype, ';')) type = Trim(++type); else type = MDNtype;
  1733.       sprintf(file, "YAMmsg-w%ldp%ld.txt", rp[0]->Win, rp[0]->Nr);
  1734.       strmfp(buf, C->TempDir, file);
  1735.       if (out = fopen(buf, "w"))
  1736.       {
  1737.          if      (!stricmp(type, "displayed"))  fprintf(out, GetStr(MSG_RE_MDNdisplay), msgdesc);
  1738.          else if (!stricmp(type, "processed"))  fprintf(out, GetStr(MSG_RE_MDNprocessed), msgdesc, mode);
  1739.          else if (!stricmp(type, "dispatched")) fprintf(out, GetStr(MSG_RE_MDNdispatched), msgdesc, mode);
  1740.          else if (!stricmp(type, "deleted"))    fprintf(out, GetStr(MSG_RE_MDNdeleted), msgdesc, mode);
  1741.          else if (!stricmp(type, "denied"))     fprintf(out, GetStr(MSG_RE_MDNdenied), msgdesc);
  1742.          else fprintf(out, GetStr(MSG_RE_MDNunknown), msgdesc, type, mode);
  1743.          fclose(out);
  1744.          DeleteFile(rp[0]->Filename);
  1745.          strcpy(rp[0]->Filename, buf);
  1746.          rp[0]->Decoded = TRUE;
  1747.          RE_SetPartInfo(rp[0]);
  1748.          if (rp[2]) RE_UndoPart(rp[2]);
  1749.          RE_UndoPart(rp[1]);
  1750.       }
  1751.       FreeStrBuf(msgdesc);
  1752.    }
  1753. }
  1754. ///
  1755. /// RE_HandleSignedMessage
  1756. //  Handles a PGP signed message, checks validity of signature
  1757. void RE_HandleSignedMessage(struct Part *frp)
  1758. {
  1759.    struct Part *rp[2];
  1760.  
  1761.    if (rp[0] = frp->Next)
  1762.    {
  1763.       if (*C->PGPCmdPath && (rp[1] = rp[0]->Next))
  1764.       {
  1765.          int error;
  1766.          struct TempFile *tf = OpenTempFile(NULL);
  1767.          char options[SIZE_LARGE];
  1768.          G->RE[frp->Win]->PGPSigned |= PGPS_MIME;
  1769.          ConvertCRLF(rp[0]->Filename, tf->Filename, TRUE);
  1770.          sprintf(options, (G->PGPVersion == 5) ? "%s -o %s +batchmode=1 +force +language=us" : "%s %s +bat +f", rp[1]->Filename, tf->Filename);
  1771.          error = PGPCommand((G->PGPVersion == 5) ? "pgpv": "pgp", options, NOERRORS|KEEPLOG);
  1772.          if (error > 0) G->RE[frp->Win]->PGPSigned |= PGPS_BADSIG;
  1773.          if (error >= 0) RE_GetSigFromLog(frp->Win, NULL);
  1774.          tf->FP = NULL; CloseTempFile(tf);
  1775.       }
  1776.       RE_DecodePart(rp[0]);
  1777.    }
  1778. }
  1779. ///
  1780. /// RE_DecryptPGP
  1781. //  Decrypts a PGP encrypted file
  1782. int RE_DecryptPGP(int winnum, char *src)
  1783. {
  1784.    FILE *fh;
  1785.    int error;
  1786.    char options[SIZE_LARGE], orcpt[SIZE_ADDRESS];
  1787.  
  1788.    *orcpt = 0;
  1789.     DB(KPrintF("RE_DecryptPGP()\n"));
  1790.    PGPGetPassPhrase();
  1791.    if (G->PGPVersion == 5)
  1792.    {
  1793.       char fname[SIZE_PATHFILE];
  1794.       sprintf(fname, "%s.asc", src); Rename(src, fname);
  1795.       sprintf(options, "%s +batchmode=1 +force +language=us", fname);
  1796.       error = PGPCommand("pgpv", options, KEEPLOG|NOERRORS);
  1797.       RE_GetSigFromLog(winnum, orcpt);
  1798.       if (*orcpt) error = 2;
  1799.       DeleteFile(fname);
  1800.    }
  1801.    else
  1802.    {
  1803.       sprintf(options, "%s +bat +f", src);
  1804.       error = PGPCommand("pgp", options, KEEPLOG|NOERRORS);
  1805.       RE_GetSigFromLog(winnum, NULL);
  1806.    }
  1807.    PGPClearPassPhrase(error < 0 || error > 1);
  1808.    if (error < 0 || error > 1) if (fh = fopen(src, "w"))
  1809.    {
  1810.       fprintf(fh, GetStr(MSG_RE_PGPNotAllowed));
  1811.       if (G->PGPVersion == 5 && *orcpt) fprintf(fh, GetStr(MSG_RE_MsgReadOnly), orcpt);
  1812.       fclose(fh);
  1813.    }
  1814.     DB(KPrintF("RE_DecryptPGP() ends\n"));
  1815.    return error;
  1816. }
  1817. ///
  1818. /// RE_HandleEncryptedMessage
  1819. //  Handles a PGP encryped message
  1820. void RE_HandleEncryptedMessage(struct Part *frp)
  1821. {
  1822.    struct Part *rp[2];
  1823.    FILE *in;
  1824.    DB(KPrintF("RE_HandleEncryptedMessage()\n"));
  1825.    if (rp[0] = frp->Next) if (rp[1] = rp[0]->Next)
  1826.    {
  1827.       if (!RE_DecryptPGP(frp->Win, rp[1]->Filename))
  1828.       {
  1829.          G->RE[frp->Win]->PGPSigned |= PGPS_OLD;
  1830.       }
  1831.       G->RE[frp->Win]->PGPEncrypted |= PGPE_MIME;
  1832.       if (ConvertCRLF(rp[1]->Filename, rp[0]->Filename, FALSE)) if (in = fopen(rp[0]->Filename, "r"))
  1833.       {
  1834.          rp[0]->ContentType = StrBufCpy(rp[0]->ContentType, "text/plain");
  1835.          rp[0]->Printable = TRUE; rp[0]->EncodingCode = ENC_NONE;
  1836.          *rp[0]->Description = 0;
  1837.          RE_ScanHeader(rp[0], in, NULL, 2);
  1838.          fclose(in);
  1839.          rp[0]->Decoded = FALSE; RE_DecodePart(rp[0]);
  1840.          RE_UndoPart(rp[1]);
  1841.       }
  1842.    }
  1843.     DB(KPrintF("RE_HandleEncryptedMessage() ends\n"));
  1844. }
  1845. ///
  1846. /// RE_LoadMessagePart
  1847. //  Decodes a single message part
  1848. void RE_LoadMessagePart(int winnum, struct Part *part)
  1849. {
  1850.    struct Part *rp, *next;
  1851.    int rsh = RE_RequiresSpecialHandling(part);
  1852.  
  1853.    switch (rsh)
  1854.    {
  1855.       case 1:  RE_HandleMDNReport(part); break;
  1856.       case 2:  RE_HandleSignedMessage(part); break;
  1857.       case 3:  RE_HandleEncryptedMessage(part); break;
  1858.       default:
  1859.       for (rp = part->Next; rp; rp = next)
  1860.       {
  1861.          next = rp->Next;
  1862.          if (RE_IsURLencoded(rp))
  1863.          {
  1864.             rp->ContentType = StrBufCpy(rp->ContentType, "text/plain");
  1865.             rp->EncodingCode = ENC_FORM;
  1866.             RE_DecodePart(rp);
  1867.          }
  1868.          else if (!stricmp(rp->ContentType, "application/pgp-keys"))
  1869.             G->RE[winnum]->PGPKey = TRUE;
  1870.          else if (rp->Nr < 2 || (rp->Printable && C->DisplayAllTexts)) RE_DecodePart(rp);
  1871.       }
  1872.    }
  1873. }
  1874. ///
  1875. /// RE_LoadMessage
  1876. //  Prepares a message for displaying
  1877. BOOL RE_LoadMessage(int winnum, int parsemode)
  1878. {
  1879.    char newfile[SIZE_PATHFILE], file[SIZE_FILE];
  1880.    struct Part *rp;
  1881.    int i;
  1882.    Busy(GetStr(MSG_BusyReading), "", 0, 0);
  1883.    RE_CleanupMessage(winnum);
  1884.    if (!StartUnpack(G->RE[winnum]->File, newfile, G->RE[winnum]->MailPtr->Folder)) return FALSE;
  1885.    strcpy(G->RE[winnum]->File, newfile);
  1886.    G->RE[winnum]->ParseMode = parsemode;
  1887.    if (rp = G->RE[winnum]->FirstPart = RE_ParseMessage(winnum, NULL, G->RE[winnum]->File, NULL))
  1888.    {
  1889.       RE_LoadMessagePart(winnum, rp);
  1890.       for (i = 0; rp; i++, rp = rp->Next) if (rp->Nr != i)
  1891.       {
  1892.          rp->Nr = i;
  1893.          sprintf(file, "YAMmsg-w%ldp%ld%s", winnum, i, strchr(rp->Filename,'.'));
  1894.          strmfp(newfile, C->TempDir, file);
  1895.          RenameFile(rp->Filename, newfile);
  1896.          strcpy(rp->Filename, newfile);
  1897.       }
  1898.    }
  1899.    BusyEnd;
  1900.    return TRUE;
  1901. }
  1902. ///
  1903. /// RE_GetPart
  1904. //  Gets a message part by its index number
  1905. struct Part *RE_GetPart(int winnum, int partnr)
  1906. {
  1907.    struct Part *part;
  1908.    for (part = G->RE[winnum]->FirstPart; part; part = part->Next) if (part->Nr == partnr) break;
  1909.    return part;
  1910. }
  1911. ///
  1912. /// RE_InitPrivateRC
  1913. //  Allocates resources for background message parsing
  1914. void RE_InitPrivateRC(struct Mail *mail, int parsemode)
  1915. {
  1916.    G->RE[4] = calloc(1,sizeof(struct RE_ClassData));
  1917.    G->RE[4]->Mail = *mail;
  1918.    G->RE[4]->MailPtr = mail;
  1919.    GetMailFile(G->RE[4]->File, mail->Folder, mail);
  1920.    RE_LoadMessage(4, parsemode);
  1921. }
  1922. ///
  1923. /// RE_FreePrivateRC
  1924. //  Frees resources used by background message parsing
  1925. void RE_FreePrivateRC(void)
  1926. {
  1927.    RE_CleanupMessage(4);
  1928.    free(G->RE[4]);
  1929. }
  1930. ///
  1931. /// AppendToBuffer
  1932. //  Appends a string to a dynamic-length buffer
  1933. char *AppendToBuffer(char *buf, int *wptr, int *len, char *add)
  1934. {
  1935.    int nlen = *len, npos = (*wptr)+strlen(add);
  1936.    while (npos >= nlen-1) nlen = (nlen*3)/2;
  1937.    if (nlen != *len) buf = realloc(buf, *len = nlen);
  1938.    while (*add) buf[(*wptr)++] = *add++;
  1939.    return buf;
  1940. }
  1941. ///
  1942. /// RE_ExtractURL
  1943. //  Extracts URL from a message line
  1944. BOOL RE_ExtractURL(char *line, char *url, char **urlptr, char **rest)
  1945. {
  1946.    char *protocols[7] = { "mailto:", "http://", "https://", "ftp://", "gopher://", "telnet://", "news:" };
  1947.    char *legalchars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@_?+-,.~/%&=:*#";
  1948.    char *foundurl = NULL, *p;
  1949.    int i;
  1950.    if (p = strchr(line, ':')) for (i = 0; i < 7; i++) if (foundurl = stristr(line, protocols[i])) break;
  1951.    if (!foundurl) return FALSE;
  1952.    for (i = 0; foundurl[i] && strchr(legalchars, foundurl[i]) && i < SIZE_URL-1; i++) url[i] = foundurl[i];
  1953.    if (strchr(".?!", url[i-1])) --i;
  1954.    url[i] = 0;
  1955.    if (urlptr) *urlptr = foundurl;
  1956.    if (rest) *rest = &foundurl[i];
  1957.    return TRUE;
  1958. }
  1959. ///
  1960. /// RE_ReadInMessage
  1961. //  Reads a message into the display buffer
  1962. char *RE_ReadInMessage(int winnum, int mode)
  1963. {
  1964.    struct RE_ClassData *re = G->RE[winnum];
  1965.    struct Part *part, *uup = NULL, *last, *first = re->FirstPart;
  1966.    char buffer[SIZE_LARGE], *msg, *cmsg, *ptr, *rptr, *eolptr, url[SIZE_URL], *urlptr, *tsb, *sb, *bo, *pl;
  1967.    int totsize, len, wptr;
  1968.    FILE *fh;
  1969.    
  1970.    DB(KPrintF("RE_ReadInMessage(%ld,%ld\n",winnum,mode));
  1971.    if (re->NoTextstyles) { tsb = "\033c\033[s:18]\033l"; sb = "\033[s:2]"; bo = "\033b"; pl = "\033n"; }
  1972.    else { tsb = "<tsb>"; sb = "<sb>"; bo = "*"; pl = "*"; }
  1973.    for (totsize = 1000, part = first; part; part = part->Next)
  1974.    {
  1975.       if (mode != RIM_READ && part->Nr && part->Nr != C->LetterPart) continue;
  1976.       if (part->Decoded || !part->Nr) totsize += part->Size; else totsize += 200;
  1977.    }
  1978.    if (cmsg = calloc(len=(totsize*3)/2,1))
  1979.    {
  1980.       if (mode != RIM_QUIET) Busy(GetStr(MSG_BusyDisplaying), "", 0, 0);
  1981.       wptr = 0;
  1982.       if (mode == RIM_READ)
  1983.          if (fh = fopen(first->Filename, "r"))
  1984.          {
  1985.             int buflen = re->FirstPart->MaxHeaderLen+4;
  1986.             char *linebuf = malloc(buflen);
  1987.             while (fgets(linebuf, buflen, fh))
  1988.                cmsg = AppendToBuffer(cmsg, &wptr, &len, linebuf);
  1989.             free(linebuf);
  1990.             fclose(fh);
  1991.             cmsg = AppendToBuffer(cmsg, &wptr, &len, "\n");
  1992.          }
  1993.       for (part = first->Next; part; part = part->Next)
  1994.       {
  1995.          BOOL dodisp = (part->Printable && part->Decoded);
  1996.          if (mode != RIM_READ && part->Nr > 1) break;
  1997.          if (mode == RIM_READ && (part->Nr > 1 || !dodisp))
  1998.          {
  1999.             *buffer = 0; sprintf(buffer, "%s%ld: %s\n%s%s:%s %s   %s%s:%s %ld %s\n", tsb, part->Nr, part->Name, bo, GetStr(MSG_RE_ContentType), pl, DescribeCT(part->ContentType), bo, GetStr(MSG_Size), pl, part->Size, GetStr(MSG_Bytes));
  2000.             if (*buffer) cmsg = AppendToBuffer(cmsg, &wptr, &len, buffer);
  2001.             *buffer = 0; if (*part->Description) sprintf(&buffer[strlen(buffer)], "%s%s:%s %s\n", bo, GetStr(MSG_RE_Description), pl, part->Description);
  2002.             if (dodisp) { strcat(buffer, sb); strcat(buffer, "\n"); }
  2003.             if (*buffer) cmsg = AppendToBuffer(cmsg, &wptr, &len, buffer);
  2004.          }
  2005.          if (dodisp)
  2006.          {
  2007.             if (fh = fopen(part->Filename, "r"))
  2008.             {       
  2009.                if (msg = calloc(part->Size+3,1))
  2010.                {
  2011.                char *sigptr;
  2012.  
  2013.                   *msg = '\n';
  2014.                   fread(msg+1, 1, part->Size, fh);
  2015.                   rptr = msg+1;
  2016.  
  2017.                   // find signature first if it should be stripped
  2018.                   if (mode == RIM_QUOTE && C->StripSignature)
  2019.                   {
  2020.                   int lines=21;
  2021.  
  2022.                      sigptr = msg + part->Size;
  2023.                      while(sigptr > msg)
  2024.                      {
  2025.                         sigptr--;
  2026.                         while((sigptr > msg) && (*sigptr != '\n')) sigptr--;  // step back to previous line
  2027.                         if((!--lines) || (sigptr <= msg+1))                   // abort after 20 lines or if at msg start
  2028.                         {
  2029.                            sigptr = NULL;
  2030.                            break;
  2031.                         }
  2032.                         if(strncmp(sigptr+1,"-- ",3) == 0)                    // check for sig separator
  2033.                         {
  2034.                            sigptr++;
  2035.                            break;
  2036.                         }
  2037.                      }
  2038.                   }
  2039.  
  2040.                   while (*rptr)
  2041.                   {
  2042.                      for (eolptr = rptr; *eolptr && *eolptr != '\n'; eolptr++); *eolptr = 0;
  2043. /* UUencoded */      if (!strncmp(rptr, "begin ", 6) && isdigit((int)rptr[6]))
  2044.                      {
  2045.                         if (!re->FirstReadDone)
  2046.                         {
  2047.                            FILE *ufh;
  2048.                            ptr = &rptr[6];
  2049.                            while (!ISpace(*ptr)) ptr++;
  2050.                            ptr = stpblk(ptr);
  2051.                            for (last = first; last->Next; last = last->Next);
  2052.                            if (ufh = RE_OpenNewPart(winnum, &uup, last, first))
  2053.                            {
  2054.                               uup->ContentType = StrBufCpy(uup->ContentType, "application/octet-stream");
  2055.                               strcpy(uup->Description, GetStr(MSG_RE_UUencodedFile));
  2056.                               stccpy(uup->Name, ptr, SIZE_FILE);
  2057.                               fromuuetxt(&rptr, ufh);
  2058.                               fclose(ufh);
  2059.                               uup->Decoded = TRUE;
  2060.                               RE_SetPartInfo(uup);
  2061.                               eolptr = rptr-1; ptr = rptr;
  2062.                            }
  2063.                            else ER_NewError(GetStr(MSG_ER_CantCreateTempfile), NULL, NULL);
  2064.                         }
  2065.                         else
  2066.                         {
  2067.                            for (ptr=eolptr+1; *ptr; ptr++)
  2068.                            {
  2069.                               if (!strncmp(ptr, "end", 3)) break;
  2070.                               while (*ptr && *ptr != '\n') ptr++;
  2071.                            }
  2072.                            while (*ptr && *ptr != '\n') ptr++; eolptr = ptr++;
  2073.                         }
  2074.                         if (!strncmp(ptr, "size", 4))
  2075.                         {
  2076.                            if (!re->FirstReadDone)
  2077.                            {
  2078.                               int expsize = atoi(&ptr[5]);
  2079.                               if (uup->Size != expsize) ER_NewError(GetStr(MSG_ER_UUSize), (char *)uup->Size, (char *)expsize);
  2080.                            }
  2081.                            for (eolptr = ptr; *eolptr && *eolptr!='\n'; eolptr++); *eolptr = 0;
  2082.                         }
  2083.                         goto rim_cont;
  2084.                      }
  2085. /* PGP message */    if (!strncmp(rptr, "-----BEGIN PGP MESSAGE", 21))
  2086.                      {
  2087.                         struct TempFile *tf;
  2088.                         DB(KPrintF("RE_ReadInMessage(): encrypted message\n"));
  2089.                         if (tf = OpenTempFile("w"))
  2090.                         {
  2091.                            *eolptr = '\n';
  2092.                            for (ptr=eolptr+1; *ptr; ptr++)
  2093.                            {
  2094.                               if (!strncmp(ptr, "-----END PGP MESSAGE", 19)) break;
  2095.                               while (*ptr && *ptr != '\n') ptr++;
  2096.                            }
  2097.                            while (*ptr && *ptr != '\n') ptr++; eolptr = ptr++;
  2098.                            fwrite(rptr, 1, ptr-rptr, tf->FP);
  2099.                            fclose(tf->FP); tf->FP = NULL;
  2100.                            DB(KPrintF("RE_ReadInMessage(): decrypting\n"));
  2101.                            if (!RE_DecryptPGP(winnum, tf->Filename)) re->PGPSigned |= PGPS_OLD;
  2102.                            if (tf->FP = fopen(tf->Filename, "r"))
  2103.                            {
  2104.                               char buf2[SIZE_LARGE];
  2105.                               DB(KPrintF("RE_ReadInMessage(): decrypted message follows\n"));
  2106.                               while (fgets(buf2, SIZE_LARGE, tf->FP))
  2107.                               {
  2108.                                  rptr = buf2;
  2109.                                  DB(KPrintF(buf2));
  2110.                                  cmsg = AppendToBuffer(cmsg, &wptr, &len, buf2);
  2111.                               }
  2112.                            }
  2113.                            CloseTempFile(tf);
  2114.                         }
  2115.                         re->PGPEncrypted |= PGPE_OLD;
  2116.                         DB(KPrintF("RE_ReadInMessage(): done with decryption\n"));
  2117.                         goto rim_cont;
  2118.                      }
  2119. /* signature */      if (!strcmp(rptr, "-- "))
  2120.                      {
  2121.                         if (mode == RIM_QUOTE && C->StripSignature && (rptr == sigptr)) break;
  2122.                         else if (mode == RIM_READ)
  2123.                         {
  2124.                            if (C->SigSepLine == 1) cmsg = AppendToBuffer(cmsg, &wptr, &len, rptr);
  2125.                            if (C->SigSepLine == 2) cmsg = AppendToBuffer(cmsg, &wptr, &len, sb);
  2126.                            if (C->SigSepLine == 3) break;
  2127.                            cmsg = AppendToBuffer(cmsg, &wptr, &len, "\n");
  2128.                            goto rim_cont;
  2129.                         }
  2130.                      }
  2131. /* URL */            if (!re->NoTextstyles && mode == RIM_READ) if (RE_ExtractURL(rptr, url, &urlptr, &ptr))
  2132.                      {
  2133.                         char *buf2, *p;
  2134.                         if (buf2 = calloc(SIZE_DEFAULT+(strlen(rptr)*3)/2,1))
  2135.                         {
  2136.                            p = buf2;
  2137.                            do
  2138.                            {
  2139.                               while (rptr < urlptr) *p++ = *rptr++;
  2140.                               sprintf(p, "\033p[7]%s\033p[0]", url);
  2141.                               p = &buf2[strlen(buf2)]; rptr = ptr;
  2142.                            } while (RE_ExtractURL(rptr, url, &urlptr, &ptr));
  2143.                            strcpy(p, rptr); strcat(p, "\n");
  2144.                            cmsg = AppendToBuffer(cmsg, &wptr, &len, buf2);
  2145.                            free(buf2);
  2146.                            goto rim_cont;
  2147.                         }
  2148.                      }
  2149.                      if (!strncmp(rptr, "-----BEGIN PGP PUBLIC KEY BLOCK", 31)) re->PGPKey = TRUE;
  2150.                      if (!strncmp(rptr, "-----BEGIN PGP SIGNED MESSAGE", 29)) re->PGPSigned |= PGPS_OLD;
  2151.                      cmsg = AppendToBuffer(cmsg, &wptr, &len, rptr);
  2152.                      cmsg = AppendToBuffer(cmsg, &wptr, &len, "\n");
  2153. rim_cont:
  2154.                      rptr = eolptr+1;
  2155.                      if (mode == RIM_QUIET) DoMethod(G->App,MUIM_Application_InputBuffered);
  2156.                   }
  2157.                   free(msg);
  2158.                }
  2159.                fclose(fh);
  2160.             }
  2161.          }
  2162.       }
  2163.       re->FirstReadDone = TRUE;
  2164.       if (mode != RIM_QUIET) BusyEnd;
  2165.    }
  2166.    DB(KPrintF("RE_ReadInMessage() ends"));
  2167.    return cmsg;
  2168. }
  2169. ///
  2170. /// RE_AddExtraHeader
  2171. //  Adds additional headers to the header listview
  2172. void RE_AddExtraHeader(APTR lv, char *header, char *value)
  2173. {
  2174.    char buffer[SIZE_LARGE];
  2175.    if (!*value) return;
  2176.    sprintf(buffer, MUIX_I"%s: %s", StripUnderscore(header), value);
  2177.    DoMethod(lv, MUIM_NList_InsertSingle, buffer, MUIV_NList_Insert_Bottom);
  2178. }
  2179. ///
  2180. /// RE_GetSenderInfo
  2181. //  Parses X-SenderInfo header field
  2182. void RE_GetSenderInfo(struct Mail *mail, struct ABEntry *ab)
  2183. {
  2184.    char *s, *t, *eq;
  2185.    struct ExtendedMail *email;
  2186.  
  2187.    clear(ab, sizeof(struct ABEntry));
  2188.    stccpy(ab->Address, mail->From.Address, SIZE_ADDRESS);
  2189.    stccpy(ab->RealName, mail->From.RealName, SIZE_REALNAME);
  2190.    if (mail->Flags & MFLAG_SENDERINFO)
  2191.    {
  2192.       email = MA_ExamineMail(mail->Folder, mail->MailFile, NULL, TRUE);
  2193.       if (s = strchr(email->SenderInfo, ';'))
  2194.       {
  2195.          *s++ = 0;
  2196.          do {
  2197.             if (t = ParamEnd(s)) *t++ = 0;
  2198.             if (!(eq = strchr(s, '='))) Cleanse(s);
  2199.             else
  2200.             {
  2201.                *eq++ = 0;
  2202.                s = Cleanse(s); eq = stpblk(eq);
  2203.                StripTrailingSpace(eq);
  2204.                UnquoteString(eq, FALSE);
  2205.                if (!stricmp(s, "street")) stccpy(ab->Street, eq, SIZE_DEFAULT);
  2206.                if (!stricmp(s, "city")) stccpy(ab->City, eq, SIZE_DEFAULT);
  2207.                if (!stricmp(s, "country")) stccpy(ab->Country, eq, SIZE_DEFAULT);
  2208.                if (!stricmp(s, "phone")) stccpy(ab->Phone, eq, SIZE_DEFAULT);
  2209.                if (!stricmp(s, "homepage")) stccpy(ab->Homepage, eq, SIZE_URL);
  2210.                if (!stricmp(s, "dob")) ab->BirthDay = atol(eq);
  2211.                if (!stricmp(s, "picture")) stccpy(ab->Photo, eq, SIZE_PATHFILE);
  2212.                ab->Type = 1;
  2213.             }
  2214.             s = t;
  2215.          } while (t);
  2216.       }
  2217.       MA_FreeEMailStruct(email);
  2218.    }
  2219. }
  2220. ///
  2221. /// RE_UpdateSenderInfo
  2222. //  Updates address book entry of sender
  2223. void RE_UpdateSenderInfo(struct ABEntry *old, struct ABEntry *new)
  2224. {
  2225.    BOOL changed = FALSE;
  2226.  
  2227.    if (!*old->RealName && *new->RealName) { strcpy(old->RealName, new->RealName); changed = TRUE; }
  2228.    if (!*old->Address  && *new->Address ) { strcpy(old->Address,  new->Address ); changed = TRUE; }
  2229.    if (!*old->Street   && *new->Street  ) { strcpy(old->Street,   new->Street  ); changed = TRUE; }
  2230.    if (!*old->Country  && *new->Country ) { strcpy(old->Country,  new->Country ); changed = TRUE; }
  2231.    if (!*old->City     && *new->City    ) { strcpy(old->City,     new->City    ); changed = TRUE; }
  2232.    if (!*old->Phone    && *new->Phone   ) { strcpy(old->Phone,    new->Phone   ); changed = TRUE; }
  2233.    if (!*old->Homepage && *new->Homepage) { strcpy(old->Homepage, new->Homepage); changed = TRUE; }
  2234.    if (!old->BirthDay  && new->BirthDay ) { old->BirthDay = new->BirthDay; changed = TRUE; }
  2235.    if (changed) AB_SaveABookFunc();
  2236. }
  2237. ///
  2238. /// RE_AddSenderInfo
  2239. //  Displays sender information to header listview
  2240. void RE_AddSenderInfo(int winnum, struct ABEntry *ab)
  2241. {
  2242.    APTR lv = G->RE[winnum]->GUI.LV_HEAD;
  2243.    RE_AddExtraHeader(lv, GetStr(MSG_EA_RealName), ab->RealName);
  2244.    RE_AddExtraHeader(lv, GetStr(MSG_EA_Street), ab->Street);
  2245.    RE_AddExtraHeader(lv, GetStr(MSG_EA_City), ab->City);
  2246.    RE_AddExtraHeader(lv, GetStr(MSG_EA_Country), ab->Country);
  2247.    RE_AddExtraHeader(lv, GetStr(MSG_EA_Phone), ab->Phone);
  2248.    RE_AddExtraHeader(lv, GetStr(MSG_EA_DOB), AB_ExpandBD(ab->BirthDay));
  2249.    RE_AddExtraHeader(lv, GetStr(MSG_EA_Description), ab->Comment);
  2250.    RE_AddExtraHeader(lv, GetStr(MSG_EA_Homepage), ab->Homepage);
  2251. }
  2252. ///
  2253. /// RE_AddToAddrbook
  2254. //  Adds sender to the address book
  2255. struct ABEntry *RE_AddToAddrbook(APTR win, struct ABEntry *templ)
  2256. {
  2257.    struct ABEntry new;
  2258.    char buf[SIZE_LARGE];
  2259.    BOOL doit = FALSE;
  2260.    switch (C->AddToAddrbook)
  2261.    {        
  2262.       case 1: if (!templ->Type) break;
  2263.       case 2: sprintf(buf, GetStr(MSG_RE_AddSender), BuildAddrName(templ->Address, templ->RealName));
  2264.               doit = MUI_Request(G->App, win, 0, NULL, GetStr(MSG_YesNoReq), buf);
  2265.               break;
  2266.       case 3: if (!templ->Type) break;
  2267.       case 4: doit = TRUE;
  2268.    }
  2269.    if (doit)
  2270.    {
  2271.       struct MUI_NListtree_TreeNode *tn = MUIV_NListtree_Insert_ListNode_Root;
  2272.       int hits = 0;
  2273.  
  2274.       if (*C->NewAddrGroup) if (!AB_SearchEntry(MUIV_NListtree_GetEntry_ListNode_Root, C->NewAddrGroup, ASM_ALIAS|ASM_GROUP, &hits, &tn))
  2275.       {
  2276.          clear(&new, sizeof(struct ABEntry));
  2277.          stccpy(new.Alias, C->NewAddrGroup, SIZE_NAME);
  2278.          stccpy(new.Comment, GetStr(MSG_RE_NewGroupTitle), SIZE_DEFAULT);
  2279.          new.Type = AET_GROUP;
  2280.          tn = (struct MUI_NListtree_TreeNode *)DoMethod(G->AB->GUI.LV_ADRESSES, MUIM_NListtree_Insert, new.Alias, &new, MUIV_NListtree_Insert_ListNode_Root, MUIV_NListtree_Insert_PrevNode_Sorted, TNF_LIST);
  2281.       }
  2282.       clear(&new, sizeof(struct ABEntry));
  2283.       new.Type = AET_USER;
  2284.       RE_UpdateSenderInfo(&new, templ);
  2285.       EA_SetDefaultAlias(&new);
  2286.       tn = (struct MUI_NListtree_TreeNode *)DoMethod(G->AB->GUI.LV_ADRESSES, MUIM_NListtree_Insert, new.Alias, &new, tn, MUIV_NListtree_Insert_PrevNode_Sorted, 0);
  2287.       if (tn)
  2288.       {
  2289.          AB_SaveABookFunc();
  2290.          return tn->tn_User;
  2291.       }
  2292.    }
  2293.    return NULL;
  2294. }
  2295. ///
  2296. /// RE_FindPhotoOnDisk
  2297. //  Searches portrait of sender in the gallery directory
  2298. BOOL RE_FindPhotoOnDisk(struct ABEntry *ab, char *photo)
  2299. {
  2300.    *photo = 0;
  2301.    if (*ab->Photo) strcpy(photo, ab->Photo);
  2302.    else if (*C->GalleryDir)
  2303.    {
  2304.       char fname[SIZE_FILE];
  2305.       stccpy(fname, ab->RealName, SIZE_FILE);
  2306.       if (PFExists(C->GalleryDir, fname)) strmfp(photo, C->GalleryDir, fname);
  2307.       else
  2308.       {
  2309.          stccpy(fname, ab->Address, SIZE_FILE);
  2310.          if (PFExists(C->GalleryDir, fname)) strmfp(photo, C->GalleryDir, fname);
  2311.       }
  2312.    }
  2313.    if (!*photo) return FALSE;
  2314.    return (BOOL)(FileSize(photo) > 0);
  2315. }
  2316. ///
  2317. /// RE_DownloadPhoto
  2318. //  Downloads portrait photograph of sender from the YAM homepage
  2319. BOOL RE_DownloadPhoto(APTR win, char *url, struct ABEntry *ab)
  2320. {
  2321.    char fname[SIZE_FILE], picfname[SIZE_PATHFILE], ext[SIZE_SMALL];
  2322.    char *name = *ab->Alias ? ab->Alias : "pic";
  2323.    int i;
  2324.    BOOL success = FALSE, doit = FALSE;
  2325.  
  2326.    switch (C->AddToAddrbook)
  2327.    {    
  2328.       case 1: case 2: doit = MUI_Request(G->App, win, 0, NULL, GetStr(MSG_OkayCancelReq), GetStr(MSG_RE_DownloadPhotoReq)); break;
  2329.       case 3: case 4: doit = TR_IsOnline();
  2330.    }
  2331.    if (doit)
  2332.    {
  2333.       if (!stcgfe(ext, url)) strcpy(ext, "iff");
  2334.       sprintf(fname, "%s.%s", name, ext);
  2335.       for (i = 2; PFExists(C->GalleryDir, fname); i++) sprintf(fname, "%s%ld.%s", name, i, ext);
  2336.       strmfp(picfname, C->GalleryDir, fname);
  2337.       if (TR_OpenTCPIP())
  2338.       {
  2339.          Busy(GetStr(MSG_BusyDownloadingPic), name, 0, 0);
  2340.          CreateDirectory(C->GalleryDir);
  2341.          if (TR_DownloadURL(url, NULL, NULL, picfname))
  2342.          {
  2343.             strcpy(ab->Photo, picfname);
  2344.             AB_SaveABookFunc();
  2345.             success = TRUE;
  2346.          }
  2347.          BusyEnd;
  2348.          TR_CloseTCPIP();
  2349.       }
  2350.       else ER_NewError(GetStr(MSG_ER_NoTCP), NULL, NULL);
  2351.    }
  2352.    return success;
  2353. }
  2354. ///
  2355. /// RE_DisplayMessage
  2356. //  Shows message header and body in read window
  2357. void RE_DisplayMessage(int winnum)
  2358. {
  2359.    char *cmsg, *body;
  2360.    BOOL dispheader;
  2361.    struct RE_GUIData *gui = &(G->RE[winnum]->GUI);
  2362.    struct Person *from = &G->RE[winnum]->Mail.From;
  2363.    struct MUI_NListtree_TreeNode *tn;
  2364.    struct ABEntry *ab = NULL, abtmpl;
  2365.    int hits = 0;
  2366.  
  2367.    if (cmsg = RE_ReadInMessage(winnum, RIM_READ))
  2368.    {
  2369.       dispheader = G->RE[winnum]->Header != 0;
  2370.       set(gui->GR_HEAD, MUIA_ShowMe, dispheader);
  2371.       set(gui->BO_BALANCE, MUIA_ShowMe, dispheader);
  2372.       DoMethod(gui->LV_HEAD, MUIM_NList_Clear);
  2373.       set(gui->LV_HEAD, MUIA_NList_Quiet, TRUE);
  2374.       body = cmsg;
  2375.       while (*body)
  2376.       {
  2377.          if (*body == '\n') { body++; break; }
  2378.          dispheader = G->RE[winnum]->Header == 2;
  2379.          if (G->RE[winnum]->Header == 1)
  2380.          {
  2381.             char header[SIZE_DEFAULT];
  2382.             int i;
  2383.             for (i = 0; !strchr("\n :", body[i]) && i < SIZE_DEFAULT-1; i++) header[i] = body[i];
  2384.             header[i] = 0;
  2385.             dispheader = MatchNoCase(header, C->ShortHeaders);
  2386.          }
  2387.          if (dispheader) DoMethod(gui->LV_HEAD, MUIM_NList_InsertSingleWrap, body, MUIV_NList_Insert_Bottom, G->RE[winnum]->WrapHeader ? WRAPCOL1 : NOWRAP, ALIGN_LEFT);
  2388.          while (*body && *body != '\n') body++;
  2389.          if (*body) body++;
  2390.       }
  2391.       if (!AB_SearchEntry(MUIV_NListtree_GetEntry_ListNode_Root, from->Address, ASM_ADDRESS|ASM_USER, &hits, &tn) && *from->RealName)
  2392.            AB_SearchEntry(MUIV_NListtree_GetEntry_ListNode_Root, from->RealName, ASM_REALNAME|ASM_USER, &hits, &tn);
  2393.       if (hits) ab = tn->tn_User;
  2394.       RE_GetSenderInfo(G->RE[winnum]->MailPtr, &abtmpl);
  2395.       if (!stricmp(from->Address, C->EmailAddress) || !stricmp(from->RealName, C->RealName))
  2396.       {
  2397.          if (!ab) { ab = &abtmpl; *ab->Photo = 0; }
  2398.       }
  2399.       else
  2400.       {
  2401.          if (ab)
  2402.          {
  2403.             RE_UpdateSenderInfo(ab, &abtmpl);
  2404.             if (!*ab->Photo && *abtmpl.Photo && *C->GalleryDir) RE_DownloadPhoto(gui->WI, abtmpl.Photo, ab);
  2405.          }
  2406.          else
  2407.          {
  2408.             if (ab = RE_AddToAddrbook(gui->WI, &abtmpl))
  2409.             {
  2410.                if (*abtmpl.Photo && *C->GalleryDir) RE_DownloadPhoto(gui->WI, abtmpl.Photo, ab);
  2411.             }
  2412.             else { ab = &abtmpl; *ab->Photo = 0; }
  2413.          }
  2414.       }
  2415.       if (G->RE[winnum]->SenderInfo)
  2416.       {
  2417.          if (hits || ab->Type == 1) RE_AddSenderInfo(winnum, ab);
  2418.          if (G->RE[winnum]->SenderInfo == 2) if (DoMethod(gui->GR_PHOTO, MUIM_Group_InitChange))
  2419.          {
  2420.             char photopath[SIZE_PATHFILE];
  2421.             if (gui->BC_PHOTO)
  2422.             {
  2423.                DoMethod(gui->GR_PHOTO, OM_REMMEMBER, gui->BC_PHOTO);
  2424.                MUI_DisposeObject(gui->BC_PHOTO);
  2425.             }
  2426.             gui->BC_PHOTO = NULL;
  2427.             if (RE_FindPhotoOnDisk(ab, photopath))
  2428.             {
  2429.                gui->BC_PHOTO = MakePicture(photopath);
  2430.                DoMethod(gui->GR_PHOTO, OM_ADDMEMBER, gui->BC_PHOTO);
  2431.             }
  2432.             DoMethod(gui->GR_PHOTO, MUIM_Group_ExitChange);
  2433.          }
  2434.       }
  2435.       set(gui->GR_INFO, MUIA_ShowMe, (G->RE[winnum]->SenderInfo == 2) && (gui->BC_PHOTO != NULL));
  2436.       set(gui->LV_HEAD, MUIA_NList_Quiet, FALSE);
  2437.       set(gui->TE_TEXT, MUIA_TextEditor_ImportHook, G->RE[winnum]->NoTextstyles ? MUIV_TextEditor_ImportHook_Plain : MUIV_TextEditor_ImportHook_EMail);
  2438.       set(gui->TE_TEXT, MUIA_TextEditor_FixedFont, G->RE[winnum]->FixedFont);
  2439.       set(gui->TE_TEXT, MUIA_TextEditor_Contents, body);
  2440.       free(cmsg);
  2441.    }
  2442. }
  2443. ///
  2444. /// RE_ClickedOnMessage
  2445. //  User clicked on a e-mail address
  2446. void RE_ClickedOnMessage(char *address)
  2447. {
  2448.    struct MUI_NListtree_TreeNode *tn;
  2449.    struct ABEntry *ab = NULL;
  2450.    int l, win, hits = 0;
  2451.    char *p, *gads, buf[SIZE_LARGE], *body = NULL, *subject = NULL;
  2452.    if (l = strlen(address)) if (strchr(".?!", address[--l])) address[l] = 0;
  2453.    for (p = strchr(address, '&'); p; p = strchr(p, '&'))
  2454.    {
  2455.       *p++ = 0;
  2456.       if (!strnicmp(p, "body=", 5)) body = &p[5];
  2457.       if (!strnicmp(p, "subject=", 8)) subject = &p[8];
  2458.    }
  2459.    if (AB_SearchEntry(MUIV_NListtree_GetEntry_ListNode_Root, address, ASM_ADDRESS|ASM_USER|ASM_LIST, &hits, &tn)) ab = tn->tn_User;
  2460.    sprintf(buf, GetStr(MSG_RE_SelectAddressReq), address);
  2461.    gads = GetStr(hits ? MSG_RE_SelectAddressEdit : MSG_RE_SelectAddressAdd);
  2462.    switch (MUI_Request(G->App, G->MA->GUI.WI, 0, NULL, gads, buf))
  2463.    {
  2464.       case 1: if ((win = MA_NewNew(NULL, 0)) >= 0)
  2465.               {
  2466.                  struct WR_GUIData *gui = &G->WR[win]->GUI;
  2467.                  setstring(gui->ST_TO, hits ? BuildAddrName(address, ab->RealName) : address);
  2468.                  if (subject) setstring(gui->ST_SUBJECT, subject);
  2469.                  if (body) set(gui->TE_EDIT, MUIA_TextEditor_Contents, body);
  2470.                  set(gui->WI, MUIA_Window_ActiveObject, gui->ST_SUBJECT);
  2471.               }
  2472.               break;
  2473.       case 2: DoMethod(G->App, MUIM_CallHook, &AB_OpenHook, ABM_EDIT);
  2474.               if (hits)
  2475.               {
  2476.                  if ((win = EA_Init(ab->Type, tn)) >= 0) EA_Setup(win, ab);
  2477.               }
  2478.               else
  2479.               {
  2480.                  if ((win = EA_Init(AET_USER, NULL)) >= 0) setstring(G->EA[win]->GUI.ST_ADDRESS, address);
  2481.               }
  2482.               break;
  2483.    }
  2484. }
  2485. ///
  2486. /// RE_DoubleClickFunc
  2487. //  Handles double-clicks on an URL
  2488. SAVEDS ASM BOOL RE_DoubleClickFunc(REG(a1,struct ClickMessage *clickmsg), REG(a2,APTR obj))
  2489. {
  2490.    int pos = clickmsg->ClickPosition;
  2491.    char *line = clickmsg->LineContents, *p, *surl;
  2492.    static char url[SIZE_URL];
  2493.  
  2494.    DoMethod(G->App, MUIM_Application_InputBuffered);
  2495.    while (pos && !ISpace(line[pos-1]) && line[pos-1] != '<') pos--;
  2496.    surl = &line[pos];
  2497.    for (p = url; !ISpace(line[pos]) && line[pos] != '>' && line[pos] != '\n' && line[pos] && p-url < SIZE_URL; pos++) *p++ = line[pos];
  2498.    *p = 0;
  2499.    if (RE_ExtractURL(surl, url, NULL, NULL))
  2500.       if (!strnicmp(url, "mailto:", 7)) RE_ClickedOnMessage(&url[7]);
  2501.       else GotoURL(url);
  2502.    else if (strchr(url, '@')) RE_ClickedOnMessage(url);
  2503.    else if (isdigit(line[0]) && (line[1] == ':' || line[2] == ':'))
  2504.    {
  2505.       int pnr = atoi(line), winnum;
  2506.       struct Part *part;
  2507.       get(_win(obj), MUIA_UserData, &winnum);
  2508.       part = RE_GetPart(winnum, pnr);
  2509.       RE_DecodePart(part);
  2510.       RE_DisplayMIME(part->Filename, part->ContentType);
  2511.    }
  2512.    else return FALSE;
  2513.    return TRUE;
  2514. }
  2515. MakeHook(RE_DoubleClickHook, RE_DoubleClickFunc);
  2516. ///
  2517. /// RE_ShowEnvFunc
  2518. //  Changes display options (header, textstyles, sender info)
  2519. SAVEDS ASM void RE_ShowEnvFunc(REG(a1,int *arg))
  2520. {
  2521.    int lev, winnum = arg[0], mode = arg[1];
  2522.    struct RE_ClassData *re = G->RE[winnum];
  2523.    long opt;
  2524.  
  2525.    get(re->GUI.SL_TEXT, MUIA_Prop_First, &lev);
  2526.    switch (mode)
  2527.    {
  2528.       case 0: case 1: case 2: re->Header = mode;
  2529.                               break;
  2530.       case 3: case 4: case 5: re->SenderInfo = mode-3;
  2531.                               break;
  2532.       case 6:  get(re->GUI.MI_WRAPH, MUIA_Menuitem_Checked, &opt);
  2533.                re->WrapHeader = opt; break;
  2534.       case 7:  get(re->GUI.MI_TSTYLE, MUIA_Menuitem_Checked, &opt);
  2535.                re->NoTextstyles = !opt; break;
  2536.       case 8:  get(re->GUI.MI_FFONT, MUIA_Menuitem_Checked, &opt);
  2537.                re->FixedFont = opt; break;
  2538.    }
  2539.    RE_DisplayMessage(winnum);
  2540.    set(re->GUI.SL_TEXT, MUIA_Prop_First, lev);
  2541. }
  2542. MakeHook(RE_ShowEnvHook, RE_ShowEnvFunc);
  2543. ///
  2544.  
  2545. /*** GUI ***/
  2546. /// RE_LV_AttachDspFunc
  2547. //  Attachment listview display hook
  2548. SAVEDS ASM long RE_LV_AttachDspFunc(REG(a2,char **array), REG(a1,struct Part *entry))
  2549. {
  2550.    if (entry)
  2551.    {
  2552.       static char dispnu[SIZE_SMALL], dispna[SIZE_CTYPE], dispsz[SIZE_SMALL];
  2553.       array[0] = array[2] = "";
  2554.       if (entry->Nr > 0) sprintf(array[0] = dispnu, "%ld", entry->Nr);
  2555.       sprintf(array[1] = dispna, *entry->Name ? entry->Name : DescribeCT(entry->ContentType));
  2556.       if (entry->Size) sprintf(array[2] = dispsz, "%s%ld", entry->Decoded ? "" : "~", entry->Size);
  2557.    }
  2558.    else
  2559.    {
  2560.          array[0] = GetStr(MSG_ATTACH_NO);
  2561.       array[1] = GetStr(MSG_ATTACH_PART);
  2562.       array[2] = GetStr(MSG_Size);
  2563.    }
  2564.  
  2565.    return 0;
  2566. }
  2567. MakeHook(RE_LV_AttachDspFuncHook,RE_LV_AttachDspFunc);
  2568. ///
  2569. /// RE_CloseFunc
  2570. //  Closes a read window
  2571. SAVEDS ASM void RE_CloseFunc(REG(a1,int *arg))
  2572. {
  2573.    int winnum = *arg;
  2574.    struct RE_ClassData *re = G->RE[winnum];
  2575.  
  2576.    RE_CleanupMessage(winnum);
  2577.    if (Virtual(re->MailPtr))
  2578.    {
  2579.       free(re->MailPtr);
  2580.       CloseTempFile(re->TempFile);
  2581.    }
  2582.    G->Weights[2] = GetMUI(re->GUI.GR_HEAD, MUIA_VertWeight);
  2583.    G->Weights[3] = GetMUI(re->GUI.GR_BODY, MUIA_VertWeight);
  2584.    DisposeModulePush(&G->RE[winnum]);
  2585. }
  2586. MakeHook(RE_CloseHook, RE_CloseFunc);
  2587. ///
  2588. /// RE_Open
  2589. //  Opens a read window
  2590. int RE_Open(int winnum, BOOL real)
  2591. {
  2592.    if (winnum < 0) for (winnum = 0; winnum < 4; winnum++) if (!G->RE[winnum]) break;
  2593.    if (winnum > 3) return -1;
  2594.    if (!G->RE[winnum])
  2595.    {
  2596.       if (!(G->RE[winnum] = RE_New(winnum, real))) return -1;
  2597.       G->RE[winnum]->Header = C->ShowHeader;
  2598.    }
  2599.    return winnum;
  2600. }
  2601. ///
  2602. /// RE_LV_HDspFunc
  2603. //  Header listview display hook
  2604. SAVEDS ASM long RE_LV_HDspFunc(REG(a2,char **array), REG(a1,char *entry))
  2605. {
  2606.    static char hfield[40];
  2607.    char *cont = entry;
  2608.    int i = 0;
  2609.  
  2610.    clear(hfield, 40);
  2611.    while (*cont != ':' && *cont && i < 38) hfield[i++] = *cont++;
  2612.    array[0] = hfield;
  2613.    array[1] = stpblk(++cont);
  2614.    return 0;
  2615. }
  2616. MakeHook(RE_LV_HDspHook,RE_LV_HDspFunc);
  2617. ///
  2618. /// RE_New
  2619. //  Creates a read window
  2620. enum {   RMEN_EDIT=501,RMEN_MOVE,RMEN_COPY,RMEN_DELETE,RMEN_PRINT,RMEN_SAVE,RMEN_DISPLAY,RMEN_DETACH,RMEN_CROP,RMEN_NEW,RMEN_REPLY,RMEN_FORWARD,RMEN_BOUNCE,RMEN_SAVEADDR,RMEN_SETUNREAD,RMEN_CHSUBJ,
  2621.          RMEN_PREV,RMEN_NEXT,RMEN_URPREV,RMEN_URNEXT,RMEN_PREVTH,RMEN_NEXTTH,
  2622.          RMEN_EXTKEY,RMEN_CHKSIG,RMEN_SAVEDEC,
  2623.          RMEN_HNONE,RMEN_HSHORT,RMEN_HFULL,RMEN_SNONE,RMEN_SDATA,RMEN_SFULL,RMEN_WRAPH,RMEN_TSTYLE,RMEN_FFONT };
  2624.  
  2625. APTR RE_LEDGroup(char *filename)
  2626. {
  2627.    return PageGroup, Child, HSpace(0), Child, MakeStatusFlag(filename), End;
  2628. }
  2629. struct RE_ClassData *RE_New(int winnum, BOOL real)
  2630. {
  2631.    struct RE_ClassData *data;
  2632.  
  2633.    if (data = calloc(1,sizeof(struct RE_ClassData)))
  2634.    {
  2635.       APTR tb_butt[14] = { MSG_RE_TBPrev,MSG_RE_TBNext,MSG_RE_TBPrevTh,MSG_RE_TBNextTh,MSG_Space,
  2636.                            MSG_RE_TBDisplay,MSG_RE_TBSave,MSG_RE_TBPrint,MSG_Space,
  2637.                            MSG_RE_TBDelete,MSG_RE_TBMove,MSG_RE_TBReply,MSG_RE_TBForward,NULL };
  2638.       APTR tb_help[14] = { MSG_HELP_RE_BT_PREVIOUS,MSG_HELP_RE_BT_NEXT,MSG_HELP_RE_BT_QUESTION,MSG_HELP_RE_BT_ANSWER,NULL,
  2639.                            MSG_HELP_RE_BT_DISPLAY,MSG_HELP_RE_BT_EXPORT,MSG_HELP_RE_BT_PRINT,NULL,
  2640.                            MSG_HELP_RE_BT_DELETE,MSG_HELP_RE_BT_MOVE,MSG_HELP_RE_BT_REPLY,MSG_HELP_RE_BT_FORWARD,NULL };
  2641.       int i;
  2642.       for (i = 0; i < 14; i++) SetupToolbar(&(data->GUI.TB_TOOLBAR[i]), tb_butt[i]?(tb_butt[i]==MSG_Space?"":GetStr(tb_butt[i])):NULL, tb_help[i]?GetStr(tb_help[i]):NULL, 0);
  2643.       data->GUI.SL_TEXT = ScrollbarObject, End;
  2644.       data->Header = C->ShowHeader;
  2645.       data->SenderInfo = C->ShowSenderInfo;
  2646.       data->WrapHeader = C->WrapHeader;
  2647.       data->NoTextstyles = !C->UseTextstyles;
  2648.       data->FixedFont = C->FixedFontEdit;
  2649.       data->GUI.WI = WindowObject,
  2650.          MUIA_Window_Title, "",
  2651.          MUIA_HelpNode, "RE_W",
  2652.          MUIA_Window_ID, MAKE_ID('R','E','A','D'),
  2653.          MUIA_UserData, winnum,
  2654.          MUIA_Window_Menustrip, MenustripObject,
  2655.             MUIA_Family_Child, MenuObject, MUIA_Menu_Title, GetStr(MSG_Message),
  2656.                MUIA_Family_Child, data->GUI.MI_EDIT = MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_MA_MEdit), MUIA_Menuitem_Shortcut,"E", MUIA_Menuitem_Enabled,real, MUIA_UserData,RMEN_EDIT, End,
  2657.                MUIA_Family_Child, MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_MA_MMove), MUIA_Menuitem_Shortcut, "M", MUIA_Menuitem_Enabled,real, MUIA_UserData,RMEN_MOVE, End,
  2658.                MUIA_Family_Child, MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_MA_MCopy),MUIA_Menuitem_Shortcut,"Y",  MUIA_UserData,RMEN_COPY, End,
  2659.                MUIA_Family_Child, MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_MA_MDelete), MUIA_Menuitem_Shortcut,"Del", MUIA_Menuitem_Enabled,real, MUIA_Menuitem_CommandString,TRUE, MUIA_UserData,RMEN_DELETE, End,
  2660.                MUIA_Family_Child, MenuitemObject, MUIA_Menuitem_Title,NM_BARLABEL, End,
  2661.                MUIA_Family_Child, MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_Print), MUIA_Menuitem_Shortcut, "P", MUIA_UserData,RMEN_PRINT, End,
  2662.                MUIA_Family_Child, MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_MA_Save), MUIA_Menuitem_Shortcut, "S", MUIA_UserData,RMEN_SAVE, End,
  2663.                MUIA_Family_Child, MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_Attachments),
  2664.                   MUIA_Family_Child, MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_RE_MDisplay), MUIA_Menuitem_Shortcut, "D", MUIA_UserData,RMEN_DISPLAY, End,
  2665.                   MUIA_Family_Child, data->GUI.MI_DETACH = MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_RE_SaveAll), MUIA_Menuitem_Shortcut,"A", MUIA_UserData,RMEN_DETACH, End,
  2666.                   MUIA_Family_Child, data->GUI.MI_CROP = MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_MA_Crop), MUIA_Menuitem_Shortcut,"O", MUIA_Menuitem_Enabled,real, MUIA_UserData,RMEN_CROP, End,
  2667.               End,
  2668.                MUIA_Family_Child, MenuitemObject, MUIA_Menuitem_Title,NM_BARLABEL, End,
  2669.                MUIA_Family_Child, MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_New), MUIA_Menuitem_Shortcut,"N", MUIA_UserData,RMEN_NEW, End,
  2670.                MUIA_Family_Child, MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_MA_MReply), MUIA_Menuitem_Shortcut,"R", MUIA_UserData,RMEN_REPLY, End,
  2671.                MUIA_Family_Child, MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_MA_MForward), MUIA_Menuitem_Shortcut,"W", MUIA_UserData,RMEN_FORWARD, End,
  2672.                MUIA_Family_Child, MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_MA_MBounce), MUIA_Menuitem_Shortcut,"B", MUIA_UserData,RMEN_BOUNCE, End,
  2673.                MUIA_Family_Child, MenuitemObject, MUIA_Menuitem_Title,NM_BARLABEL, End,
  2674.                MUIA_Family_Child, MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_MA_MGetAddress), MUIA_Menuitem_Shortcut,"J", MUIA_UserData,RMEN_SAVEADDR, End,
  2675.                MUIA_Family_Child, MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_RE_SetUnread), MUIA_Menuitem_Shortcut,"U", MUIA_UserData,RMEN_SETUNREAD, End,
  2676.                MUIA_Family_Child, MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_MA_ChangeSubj), MUIA_Menuitem_Enabled,real, MUIA_UserData,RMEN_CHSUBJ, End,
  2677.             End,
  2678.             MUIA_Family_Child, MenuObject, MUIA_Menu_Title, GetStr(MSG_RE_Navigation),
  2679.                MUIA_Family_Child, MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_RE_MNext),  MUIA_Menuitem_Shortcut, "right", MUIA_Menuitem_Enabled,real, MUIA_Menuitem_CommandString,TRUE, MUIA_UserData,RMEN_NEXT, End,
  2680.                MUIA_Family_Child, MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_RE_MPrev),  MUIA_Menuitem_Shortcut, "left", MUIA_Menuitem_Enabled,real, MUIA_Menuitem_CommandString,TRUE, MUIA_UserData,RMEN_PREV, End,
  2681.                MUIA_Family_Child, MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_RE_MURNext),MUIA_Menuitem_Shortcut, "shift right", MUIA_Menuitem_Enabled,real, MUIA_Menuitem_CommandString,TRUE, MUIA_UserData,RMEN_URNEXT, End,
  2682.                MUIA_Family_Child, MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_RE_MURPrev),MUIA_Menuitem_Shortcut, "shift right", MUIA_Menuitem_Enabled,real, MUIA_Menuitem_CommandString,TRUE, MUIA_UserData,RMEN_URPREV, End,
  2683.                MUIA_Family_Child, MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_RE_MNextTh),MUIA_Menuitem_Shortcut, ">", MUIA_Menuitem_Enabled,real, MUIA_UserData,RMEN_NEXTTH, End,
  2684.                MUIA_Family_Child, MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_RE_MPrevTh),MUIA_Menuitem_Shortcut, "<", MUIA_Menuitem_Enabled,real, MUIA_UserData,RMEN_PREVTH, End,
  2685.             End,
  2686.             MUIA_Family_Child, MenuObject, MUIA_Menu_Title, "PGP",
  2687.                MUIA_Family_Child, data->GUI.MI_EXTKEY = MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_RE_ExtractKey), MUIA_Menuitem_Shortcut,"X", MUIA_UserData,RMEN_EXTKEY, End,
  2688.                MUIA_Family_Child, data->GUI.MI_CHKSIG = MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_RE_SigCheck), MUIA_Menuitem_Shortcut,"K", MUIA_UserData,RMEN_CHKSIG, End,
  2689.                MUIA_Family_Child, data->GUI.MI_SAVEDEC = MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_RE_SaveDecrypted), MUIA_Menuitem_Shortcut,"V", MUIA_UserData,RMEN_SAVEDEC, End,
  2690.             End,
  2691.             MUIA_Family_Child, MenuObject, MUIA_Menu_Title, GetStr(MSG_MA_Settings),
  2692.                MUIA_Family_Child, MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_RE_NoHeaders),  MUIA_Menuitem_Shortcut,"0", MUIA_Menuitem_Checkit,TRUE, MUIA_Menuitem_Checked,data->Header==0, MUIA_Menuitem_Exclude,0x06, MUIA_UserData,RMEN_HNONE, End,
  2693.                MUIA_Family_Child, MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_RE_ShortHeaders), MUIA_Menuitem_Shortcut,"1", MUIA_Menuitem_Checkit,TRUE, MUIA_Menuitem_Checked,data->Header==1, MUIA_Menuitem_Exclude,0x05, MUIA_UserData,RMEN_HSHORT, End,
  2694.                MUIA_Family_Child, MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_RE_FullHeaders),  MUIA_Menuitem_Shortcut,"2", MUIA_Menuitem_Checkit,TRUE, MUIA_Menuitem_Checked,data->Header==2, MUIA_Menuitem_Exclude,0x03, MUIA_UserData,RMEN_HFULL, End,
  2695.                MUIA_Family_Child, MenuitemObject, MUIA_Menuitem_Title,NM_BARLABEL, End,
  2696.                MUIA_Family_Child, MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_RE_NoSInfo), MUIA_Menuitem_Shortcut,"3", MUIA_Menuitem_Checkit,TRUE, MUIA_Menuitem_Checked,data->SenderInfo==0, MUIA_Menuitem_Exclude,0x60, MUIA_UserData,RMEN_SNONE, End,
  2697.                MUIA_Family_Child, MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_RE_SInfo), MUIA_Menuitem_Shortcut,"4", MUIA_Menuitem_Checkit,TRUE, MUIA_Menuitem_Checked,data->SenderInfo==1, MUIA_Menuitem_Exclude,0x50, MUIA_UserData,RMEN_SDATA, End,
  2698.                MUIA_Family_Child, MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_RE_SInfoImage), MUIA_Menuitem_Shortcut,"5", MUIA_Menuitem_Checkit,TRUE, MUIA_Menuitem_Checked,data->SenderInfo==2, MUIA_Menuitem_Exclude,0x30, MUIA_UserData,RMEN_SFULL, End,
  2699.                MUIA_Family_Child, MenuitemObject, MUIA_Menuitem_Title,NM_BARLABEL, End,
  2700.                MUIA_Family_Child, data->GUI.MI_WRAPH = MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_RE_WrapHeader), MUIA_Menuitem_Shortcut,"H", MUIA_Menuitem_Checkit,TRUE, MUIA_Menuitem_Checked,data->WrapHeader, MUIA_Menuitem_Toggle,TRUE, MUIA_UserData,RMEN_WRAPH, End,
  2701.                MUIA_Family_Child, data->GUI.MI_TSTYLE = MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_RE_Textstyles), MUIA_Menuitem_Shortcut,"T", MUIA_Menuitem_Checkit,TRUE, MUIA_Menuitem_Checked,!data->NoTextstyles, MUIA_Menuitem_Toggle,TRUE, MUIA_UserData,RMEN_TSTYLE, End,
  2702. //             MUIA_Family_Child, data->GUI.MI_FFONT = MenuitemObject, MUIA_Menuitem_Title,GetStr(MSG_RE_FixedFont), MUIA_Menuitem_Shortcut,"F", MUIA_Menuitem_Checkit,TRUE, MUIA_Menuitem_Checked,data->FixedFont, MUIA_Menuitem_Toggle,TRUE, MUIA_UserData,RMEN_FFONT, End,
  2703.             End,
  2704.          End,
  2705.          WindowContents, VGroup,
  2706.             Child, (C->HideGUIElements & HIDE_TBAR) ?
  2707.                (RectangleObject, MUIA_ShowMe, FALSE, End) :
  2708.                (HGroup, GroupSpacing(0),
  2709.                   Child, HGroupV,
  2710.                      Child, data->GUI.TO_TOOLBAR = ToolbarObject,
  2711.                         MUIA_HelpNode, "RE_B",
  2712.                         MUIA_Toolbar_ImageType,      MUIV_Toolbar_ImageType_File,
  2713.                         MUIA_Toolbar_ImageNormal,    "PROGDIR:Icons/Read.toolbar",
  2714.                         MUIA_Toolbar_ImageGhost,     "PROGDIR:Icons/Read_G.toolbar",
  2715.                         MUIA_Toolbar_ImageSelect,    "PROGDIR:Icons/Read_S.toolbar",
  2716.                         MUIA_Toolbar_Description,    data->GUI.TB_TOOLBAR,
  2717.                         MUIA_Toolbar_ParseUnderscore,TRUE,
  2718.                         MUIA_Font,                   MUIV_Font_Tiny,
  2719.                         MUIA_ShortHelp, TRUE,
  2720.                      End,
  2721.                      Child, HSpace(0),
  2722.                   End,
  2723.                   Child, HSpace(8),
  2724.                   Child, VGroup,
  2725.                      Child, VSpace(0),
  2726.                      Child, HGroup,
  2727.                         TextFrame,
  2728.                         MUIA_Group_Spacing, 1,
  2729.                         MUIA_Background, MUII_TextBack,
  2730.                         Child, data->GUI.GR_STATUS[0] = PageGroup,
  2731.                            Child, HSpace(0),
  2732.                            Child, MakeStatusFlag("status_unread"),
  2733.                            Child, MakeStatusFlag("status_old"),
  2734.                            Child, MakeStatusFlag("status_forward"),
  2735.                            Child, MakeStatusFlag("status_reply"),
  2736.                            Child, MakeStatusFlag("status_waitsend"),
  2737.                            Child, MakeStatusFlag("status_error"),
  2738.                            Child, MakeStatusFlag("status_hold"),
  2739.                            Child, MakeStatusFlag("status_sent"),
  2740.                            Child, MakeStatusFlag("status_new"),
  2741.                         End,
  2742.                         Child, data->GUI.GR_STATUS[1] = RE_LEDGroup("status_crypt"),
  2743.                         Child, data->GUI.GR_STATUS[2] = RE_LEDGroup("status_signed"),
  2744.                      End,
  2745.                      Child, VSpace(0),
  2746.                   End,
  2747.                End),
  2748.             Child, VGroup,
  2749.                Child, data->GUI.GR_HEAD = HGroup, GroupSpacing(0),
  2750.                   MUIA_ShowMe, data->Header > 0,
  2751.                   MUIA_VertWeight, G->Weights[2],
  2752.                   Child, NListviewObject,
  2753.                      MUIA_NListview_NList, data->GUI.LV_HEAD = NListObject,
  2754.                         InputListFrame,
  2755.                         MUIA_NList_ConstructHook, MUIV_NList_ConstructHook_String,
  2756.                         MUIA_NList_DestructHook, MUIV_NList_DestructHook_String,
  2757.                         MUIA_NList_DisplayHook, &RE_LV_HDspHook,
  2758.                         MUIA_NList_Format, "P=\033r\0338 W=-1 MIW=-1,",
  2759.                         MUIA_NList_Input, FALSE,
  2760.                         MUIA_NList_TypeSelect, MUIV_NList_TypeSelect_Char,
  2761.                         MUIA_NList_DefaultObjectOnClick, FALSE,
  2762.                         MUIA_ContextMenu, NULL,
  2763.                         MUIA_CycleChain, 1,
  2764.                      End,
  2765.                   End,
  2766.                   Child, data->GUI.GR_INFO = ScrollgroupObject,
  2767.                      MUIA_ShowMe, FALSE,
  2768.                      MUIA_Scrollgroup_FreeHoriz, FALSE,
  2769.                      MUIA_HorizWeight, 0,
  2770.                      MUIA_Scrollgroup_Contents, data->GUI.GR_PHOTO = VGroupV, GroupSpacing(0),
  2771.                         InputListFrame,
  2772.                         Child, HVSpace,
  2773.                      End,
  2774.                   End,
  2775.                End,
  2776.                Child, data->GUI.BO_BALANCE = BalanceObject,
  2777.                   MUIA_ShowMe, data->Header > 0,
  2778.                End,
  2779.                Child, data->GUI.GR_BODY = HGroup,
  2780.                   MUIA_VertWeight, G->Weights[3],
  2781.                   MUIA_Group_Spacing, 0,
  2782.                   Child, data->GUI.TE_TEXT = NewObject(CL_TextEditor->mcc_Class,NULL,
  2783.                      InputListFrame,
  2784.                      MUIA_TextEditor_Slider, data->GUI.SL_TEXT,
  2785.                      MUIA_TextEditor_FixedFont, data->FixedFont,
  2786.                      MUIA_TextEditor_DoubleClickHook, &RE_DoubleClickHook,
  2787.                      MUIA_TextEditor_ImportHook, MUIV_TextEditor_ImportHook_EMail,
  2788.                      MUIA_TextEditor_ExportHook, MUIV_TextEditor_ExportHook_Plain,
  2789.                      MUIA_TextEditor_ReadOnly, TRUE,
  2790.                      MUIA_TextEditor_ColorMap, G->EdColMap,
  2791.                      MUIA_CycleChain, TRUE,
  2792.                   End,
  2793.                   Child, data->GUI.SL_TEXT,
  2794.                End,
  2795.             End,
  2796.          End,
  2797.       End;
  2798.       if (data->GUI.WI)
  2799.       {
  2800.          DoMethod(G->App, OM_ADDMEMBER, data->GUI.WI);
  2801.          set(data->GUI.WI,MUIA_Window_DefaultObject,data->GUI.TE_TEXT);
  2802.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_EDIT            ,MUIV_Notify_Application,6,MUIM_CallHook,&RE_NewHook,NEW_EDIT,0,winnum,FALSE);
  2803.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_MOVE            ,MUIV_Notify_Application,3,MUIM_CallHook,&RE_MoveHook,winnum);
  2804.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_COPY            ,MUIV_Notify_Application,3,MUIM_CallHook,&RE_CopyHook,winnum);
  2805.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_DELETE          ,MUIV_Notify_Application,5,MUIM_CallHook,&RE_DeleteHook,0,winnum,FALSE);
  2806.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_PRINT           ,MUIV_Notify_Application,3,MUIM_CallHook,&RE_PrintHook,winnum);
  2807.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_SAVE            ,MUIV_Notify_Application,3,MUIM_CallHook,&RE_SaveHook,winnum);
  2808.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_DISPLAY         ,MUIV_Notify_Application,3,MUIM_CallHook,&RE_DisplayHook,winnum);
  2809.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_DETACH          ,MUIV_Notify_Application,3,MUIM_CallHook,&RE_SaveAllHook,winnum);
  2810.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_CROP            ,MUIV_Notify_Application,3,MUIM_CallHook,&RE_RemoveAttachHook,winnum);
  2811.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_NEW             ,MUIV_Notify_Application,6,MUIM_CallHook,&RE_NewHook,NEW_NEW,0,winnum,FALSE);
  2812.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_REPLY           ,MUIV_Notify_Application,6,MUIM_CallHook,&RE_NewHook,NEW_REPLY,0,winnum,FALSE);
  2813.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_FORWARD         ,MUIV_Notify_Application,6,MUIM_CallHook,&RE_NewHook,NEW_FORWARD,0,winnum,FALSE);
  2814.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_BOUNCE          ,MUIV_Notify_Application,6,MUIM_CallHook,&RE_NewHook,NEW_BOUNCE,0,winnum,FALSE);
  2815.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_SAVEADDR        ,MUIV_Notify_Application,3,MUIM_CallHook,&RE_GetAddressHook,winnum);
  2816.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_SETUNREAD       ,MUIV_Notify_Application,3,MUIM_CallHook,&RE_SetUnreadHook,winnum);
  2817.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_CHSUBJ          ,MUIV_Notify_Application,3,MUIM_CallHook,&RE_ChangeSubjectHook,winnum);
  2818.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_PREV            ,MUIV_Notify_Application,6,MUIM_CallHook,&RE_PrevNextHook,-1,0,winnum,FALSE);
  2819.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_NEXT            ,MUIV_Notify_Application,6,MUIM_CallHook,&RE_PrevNextHook,1,0,winnum,FALSE);
  2820.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_URPREV          ,MUIV_Notify_Application,6,MUIM_CallHook,&RE_PrevNextHook,-1,IEQUALIFIER_LSHIFT,winnum,FALSE);
  2821.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_URNEXT          ,MUIV_Notify_Application,6,MUIM_CallHook,&RE_PrevNextHook,1,IEQUALIFIER_LSHIFT,winnum,FALSE);
  2822.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_PREVTH          ,MUIV_Notify_Application,4,MUIM_CallHook,&RE_FollowHook,-1,winnum);
  2823.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_NEXTTH          ,MUIV_Notify_Application,4,MUIM_CallHook,&RE_FollowHook,1,winnum);
  2824.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_EXTKEY          ,MUIV_Notify_Application,3,MUIM_CallHook,&RE_ExtractKeyHook,winnum);
  2825.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_CHKSIG          ,MUIV_Notify_Application,4,MUIM_CallHook,&RE_CheckSignatureHook,TRUE,winnum);
  2826.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_SAVEDEC         ,MUIV_Notify_Application,3,MUIM_CallHook,&RE_SaveDecryptedHook,winnum);
  2827.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_HNONE           ,MUIV_Notify_Application,4,MUIM_CallHook,&RE_ShowEnvHook,winnum,0);
  2828.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_HSHORT          ,MUIV_Notify_Application,4,MUIM_CallHook,&RE_ShowEnvHook,winnum,1);
  2829.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_HFULL           ,MUIV_Notify_Application,4,MUIM_CallHook,&RE_ShowEnvHook,winnum,2);
  2830.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_SNONE           ,MUIV_Notify_Application,4,MUIM_CallHook,&RE_ShowEnvHook,winnum,3);
  2831.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_SDATA           ,MUIV_Notify_Application,4,MUIM_CallHook,&RE_ShowEnvHook,winnum,4);
  2832.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_SFULL           ,MUIV_Notify_Application,4,MUIM_CallHook,&RE_ShowEnvHook,winnum,5);
  2833.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_WRAPH           ,MUIV_Notify_Application,4,MUIM_CallHook,&RE_ShowEnvHook,winnum,6);
  2834.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_TSTYLE          ,MUIV_Notify_Application,4,MUIM_CallHook,&RE_ShowEnvHook,winnum,7);
  2835.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_MenuAction   ,RMEN_FFONT           ,MUIV_Notify_Application,4,MUIM_CallHook,&RE_ShowEnvHook,winnum,8);
  2836.          DoMethod(data->GUI.TO_TOOLBAR ,MUIM_Toolbar_Notify, 0, MUIV_Toolbar_Notify_Pressed,MUIV_EveryTime, MUIV_Notify_Application,6,MUIM_CallHook,&RE_PrevNextHook,-1,MUIV_Toolbar_Qualifier,winnum,MUIV_TriggerValue);
  2837.          DoMethod(data->GUI.TO_TOOLBAR ,MUIM_Toolbar_Notify, 1, MUIV_Toolbar_Notify_Pressed,MUIV_EveryTime, MUIV_Notify_Application,6,MUIM_CallHook,&RE_PrevNextHook,1,MUIV_Toolbar_Qualifier,winnum,MUIV_TriggerValue);
  2838.          DoMethod(data->GUI.TO_TOOLBAR ,MUIM_Toolbar_Notify, 2, MUIV_Toolbar_Notify_Pressed,FALSE, MUIV_Notify_Application,4,MUIM_CallHook,&RE_FollowHook,-1,winnum);
  2839.          DoMethod(data->GUI.TO_TOOLBAR ,MUIM_Toolbar_Notify, 3, MUIV_Toolbar_Notify_Pressed,FALSE, MUIV_Notify_Application,4,MUIM_CallHook,&RE_FollowHook,1,winnum);
  2840.          DoMethod(data->GUI.TO_TOOLBAR ,MUIM_Toolbar_Notify, 5, MUIV_Toolbar_Notify_Pressed,FALSE, MUIV_Notify_Application,3,MUIM_CallHook,&RE_DisplayHook,winnum);
  2841.          DoMethod(data->GUI.TO_TOOLBAR ,MUIM_Toolbar_Notify, 6, MUIV_Toolbar_Notify_Pressed,FALSE, MUIV_Notify_Application,3,MUIM_CallHook,&RE_SaveHook,winnum);
  2842.          DoMethod(data->GUI.TO_TOOLBAR ,MUIM_Toolbar_Notify, 7, MUIV_Toolbar_Notify_Pressed,FALSE, MUIV_Notify_Application,3,MUIM_CallHook,&RE_PrintHook,winnum);
  2843.          DoMethod(data->GUI.TO_TOOLBAR ,MUIM_Toolbar_Notify, 9, MUIV_Toolbar_Notify_Pressed,MUIV_EveryTime, MUIV_Notify_Application,5,MUIM_CallHook,&RE_DeleteHook,MUIV_Toolbar_Qualifier,winnum,MUIV_TriggerValue);
  2844.          DoMethod(data->GUI.TO_TOOLBAR ,MUIM_Toolbar_Notify,10, MUIV_Toolbar_Notify_Pressed,FALSE, MUIV_Notify_Application,3,MUIM_CallHook,&RE_MoveHook,winnum);
  2845.          DoMethod(data->GUI.TO_TOOLBAR ,MUIM_Toolbar_Notify,11, MUIV_Toolbar_Notify_Pressed,MUIV_EveryTime, MUIV_Notify_Application,6,MUIM_CallHook,&RE_NewHook,NEW_REPLY,MUIV_Toolbar_Qualifier,winnum,MUIV_TriggerValue);
  2846.          DoMethod(data->GUI.TO_TOOLBAR ,MUIM_Toolbar_Notify,12, MUIV_Toolbar_Notify_Pressed,MUIV_EveryTime, MUIV_Notify_Application,6,MUIM_CallHook,&RE_NewHook,NEW_FORWARD,MUIV_Toolbar_Qualifier,winnum,MUIV_TriggerValue);
  2847.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_CloseRequest ,TRUE                 ,MUIV_Notify_Application,3,MUIM_CallHook,&RE_CloseHook,winnum);
  2848.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_InputEvent   ,"-repeat del"        ,MUIV_Notify_Application,5,MUIM_CallHook,&RE_DeleteHook,0,winnum,FALSE);
  2849.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_InputEvent   ,"-repeat shift del"  ,MUIV_Notify_Application,5,MUIM_CallHook,&RE_DeleteHook,IEQUALIFIER_LSHIFT,winnum,FALSE);
  2850.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_InputEvent   ,"-repeat space"      ,data->GUI.TE_TEXT      ,2,MUIM_TextEditor_ARexxCmd,"Next Page");
  2851.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_InputEvent   ,"-repeat backspace"  ,data->GUI.TE_TEXT      ,2,MUIM_TextEditor_ARexxCmd,"Previous Page");
  2852.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_InputEvent   ,"-repeat left"       ,MUIV_Notify_Application,5,MUIM_CallHook,&RE_PrevNextHook,-1,False,winnum);
  2853.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_InputEvent   ,"-repeat right"      ,MUIV_Notify_Application,5,MUIM_CallHook,&RE_PrevNextHook,1,False,winnum);
  2854.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_InputEvent   ,"-repeat shift left" ,MUIV_Notify_Application,5,MUIM_CallHook,&RE_PrevNextHook,-1,True,winnum);
  2855.          DoMethod(data->GUI.WI         ,MUIM_Notify,MUIA_Window_InputEvent   ,"-repeat shift right",MUIV_Notify_Application,5,MUIM_CallHook,&RE_PrevNextHook,1,True,winnum);
  2856.          return data;
  2857.       }
  2858.       free(data);
  2859.    }
  2860.    return NULL;
  2861. }
  2862. ///
  2863.